defmodule CA.CSR do @moduledoc "CA/CSR library." def ca do root("secp384r1", rdn: "/C=UA/L=Київ/O=SYNRC/CN=CA") end def server(name) do server("secp384r1", rdn: "/C=UA/L=Київ/O=SYNRC/CN=#{name}", cn: "#{name}") end def client(name) do client("secp384r1", rdn: "/C=UA/L=Київ/O=SYNRC/CN=#{name}", cn: "#{name}") end def dir(profile) do "synrc/ecc/#{profile}/" end def root(profile, rdn: rdn) do :filelib.ensure_dir(dir(profile)) ca_key = X509.PrivateKey.new_ec(:erlang.binary_to_atom(profile)) :logger.info(~c"CSR CMP DN ~p~n", [rdn]) subject_rdn = map_utf8_tags(X509.RDNSequence.new(rdn)) ca = X509.Certificate.self_signed(ca_key, subject_rdn, template: %X509.Certificate.Template{ # 25 years validity: round(25 * 365.2425), hash: :sha256, extensions: [ basic_constraints: X509.Certificate.Extension.basic_constraints(true, 1), key_usage: X509.Certificate.Extension.key_usage([:digitalSignature, :keyCertSign, :cRLSign]), subject_key_identifier: true, authority_key_identifier: true ] } ) password = :application.get_env(:ca, :password, "0000") pem = X509.PrivateKey.to_pem(ca_key, password: password) :file.write_file("#{dir(profile)}/ca.key", pem) :file.write_file("#{dir(profile)}/ca.pem", X509.Certificate.to_pem(ca)) {ca_key, ca} end def server(profile, rdn: rdn, cn: user) do {ca_key, ca} = read_ca(profile) :filelib.ensure_dir(dir(profile)) priv = X509.PrivateKey.new_ec(:erlang.binary_to_atom(profile)) password = :application.get_env(:ca, :password, "0000") pem = X509.PrivateKey.to_pem(priv, password: password) :file.write_file("#{dir(profile)}/#{user}.key", pem) :logger.info(~c"CSR SERVER DN ~p~n", [rdn]) subject_rdn = map_utf8_tags(X509.RDNSequence.new(rdn)) csr = X509.CSR.new(priv, subject_rdn, extension_request: [ X509.Certificate.Extension.subject_alt_name(["synrc.com"]) ] ) :file.write_file("#{dir(profile)}/#{user}.csr", X509.CSR.to_pem(csr)) true = X509.CSR.valid?(csr) subject = X509.CSR.subject(csr) |> map_utf8_tags() server = X509.Certificate.new(X509.CSR.public_key(csr), subject, ca, ca_key, extensions: [subject_alt_name: X509.Certificate.Extension.subject_alt_name(["synrc.com"])] ) :file.write_file("#{dir(profile)}/#{user}.cer", X509.Certificate.to_pem(server)) csr end def client(profile, rdn: rdn, cn: user) do priv = X509.PrivateKey.new_ec(:erlang.binary_to_atom(profile)) password = :application.get_env(:ca, :password, "0000") pem = X509.PrivateKey.to_pem(priv, password: password) :file.write_file("#{dir(profile)}/#{user}.key", pem) :logger.info(~c"CSR CLIENT DN ~p~n", [rdn]) subject_rdn = map_utf8_tags(X509.RDNSequence.new(rdn)) csr = X509.CSR.new(priv, subject_rdn, extension_request: [ X509.Certificate.Extension.subject_alt_name(["synrc.com"]) ] ) :file.write_file("#{dir(profile)}/#{user}.csr", X509.CSR.to_pem(csr)) csr end def init(profile) do case :filelib.is_regular("#{CA.CSR.dir(profile)}/ca.key") do false -> root(profile, rdn: "/C=UA/L=Київ/O=SYNRC/CN=CA") true -> [] end end def read_ca(profile) do init(profile) {:ok, ca_key_bin} = :file.read_file("#{CA.CSR.dir(profile)}/ca.key") {:ok, ca_bin} = :file.read_file("#{CA.CSR.dir(profile)}/ca.pem") password = :application.get_env(:ca, :password, "0000") ca_key = case X509.PrivateKey.from_pem(ca_key_bin, password: password) do {:ok, key} -> key _ -> case X509.PrivateKey.from_pem(ca_key_bin) do {:ok, key} -> key end end {:ok, ca} = X509.Certificate.from_pem(ca_bin) {ca_key, ca} end def read_ca_public(profile) do init(profile) {:ok, ca_bin} = :file.read_file("#{CA.CSR.dir(profile)}/ca.pem") {:ok, ca} = X509.Certificate.from_pem(ca_bin) {:ok, bin} = :PKIX1Explicit88.encode(:Certificate, CA.RDN.encodeAttrsCert(ca)) bin end defp map_utf8_tags({:rdnSequence, list}) do tag = case System.otp_release() |> String.to_integer() do ver when ver >= 26 -> :utf8String _ -> :uTF8String end {:rdnSequence, Enum.map(list, fn sub_list -> Enum.map(sub_list, fn {:SingleAttribute, oid, {t, val}} when t in [:utf8String, :uTF8String] -> {:SingleAttribute, oid, {tag, val}} {:AttributeTypeAndValue, oid, {t, val}} when t in [:utf8String, :uTF8String] -> {:AttributeTypeAndValue, oid, {tag, val}} other -> other end) end)} end defp map_utf8_tags(other), do: other end