diff --git a/include/DSTU-Cert.hrl b/include/DSTU-Cert.hrl new file mode 100644 index 0000000..7ab1dfc --- /dev/null +++ b/include/DSTU-Cert.hrl @@ -0,0 +1,80 @@ +%% Generated by the Erlang ASN.1 compiler. Version: 5.0.17 +%% Purpose: Erlang record definitions for each named and unnamed +%% SEQUENCE and SET, and macro definitions for each value +%% definition in module DSTU-Cert. + +-ifndef(_DSTU_CERT_HRL_). +-define(_DSTU_CERT_HRL_, true). + +-record('Certificate', { + tbsCertificate, + signatureAlgorithm, + signatureValue +}). + +-record('TBSCertificate', { + version, + serialNumber, + signature, + issuer, + validity, + subject, + subjectPublicKeyInfo, + issuerUniqueID = asn1_NOVALUE, + subjectUniqueID = asn1_NOVALUE, + extensions +}). + +-record('AttributeTypeAndValue', { + type, + value +}). + +-record('Extension', { + extnID, + critical = asn1_DEFAULT, + extnvalue +}). + +-record('Validity', { + notBefore, + notAfter +}). + +-record('SubjectPublicKeyInfo', { + algorithm, + subjectPublicKey +}). + +-record('AlgorithmIdentifier', { + algorithm, + parameters +}). + +-record('DSTU4145Params', { + definition, + dke = asn1_NOVALUE +}). + +-record('ECBinary', { + version = asn1_DEFAULT, + f, + a, + b, + n, + bp +}). + +-record('BinaryField', { + m, + p = asn1_NOVALUE +}). + +-record('Pentanomial', { + k, + j, + l +}). + +-define('id-at', {2,5,4}). +-endif. %% _DSTU_CERT_HRL_ diff --git a/include/PKIX-CommonTypes-2009.hrl b/include/PKIX-CommonTypes-2009.hrl new file mode 100644 index 0000000..4dc170c --- /dev/null +++ b/include/PKIX-CommonTypes-2009.hrl @@ -0,0 +1,30 @@ +%% Generated by the Erlang ASN.1 compiler. Version: 5.0.17 +%% Purpose: Erlang record definitions for each named and unnamed +%% SEQUENCE and SET, and macro definitions for each value +%% definition in module PKIX-CommonTypes-2009. + +-ifndef(_PKIX_COMMONTYPES_2009_HRL_). +-define(_PKIX_COMMONTYPES_2009_HRL_, true). + +-record('AttributeSet', { + type, + values +}). + +-record('SingleAttribute', { + type, + value +}). + +-record('Extension', { + extnID, + critical = asn1_DEFAULT, + extnValue +}). + +-record('SecurityCategory', { + type, + value +}). + +-endif. %% _PKIX_COMMONTYPES_2009_HRL_ diff --git a/lib/encryption/cms.ex b/lib/encryption/cms.ex index bd3ecd6..53397bc 100644 --- a/lib/encryption/cms.ex +++ b/lib/encryption/cms.ex @@ -1,142 +1,149 @@ defmodule CA.CMS do - @moduledoc "CA/CMS library." - - def map(:'dhSinglePass-stdDH-sha512kdf-scheme'), do: {:kdf, :sha512} - def map(:'dhSinglePass-stdDH-sha384kdf-scheme'), do: {:kdf, :sha384} - def map(:'dhSinglePass-stdDH-sha256kdf-scheme'), do: {:kdf, :sha256} - def map(:'dhSinglePass-stdDH-hkdf-sha256-scheme'), do: {:hkdf, :sha256} - def map(:'dhSinglePass-stdDH-hkdf-sha384-scheme'), do: {:hkdf, :sha384} - def map(:'dhSinglePass-stdDH-hkdf-sha512-scheme'), do: {:hkdf, :sha512} - - def sharedInfo(ukm, len), do: {:'ECC-CMS-SharedInfo', - {:'KeyWrapAlgorithm',{2,16,840,1,101,3,4,1,45},:asn1_NOVALUE}, ukm, <>} - - # CMS Codec KARI: ECC+KDF/ECB+AES/KW+256/CBC - - def kari(kari, privateKeyBin, schemeOID, encOID, data, iv) do - {:'KeyAgreeRecipientInfo',:v3,{_,{_,_,publicKey}},ukm,{_,kdfOID,_},[{_,_,encryptedKey}]} = kari - {scheme,_} = CA.ALG.lookup(schemeOID) - {kdf,_} = CA.ALG.lookup(kdfOID) - {enc,_} = CA.ALG.lookup(encOID) - sharedKey = :crypto.compute_key(:ecdh,publicKey,privateKeyBin,scheme) - {_,payload} = :'CMSECCAlgs-2009-02'.encode(:'ECC-CMS-SharedInfo', sharedInfo(ukm,256)) - derived = case map(kdf) do - {:kdf,hash} -> CA.KDF.derive({:kdf,hash}, sharedKey, 32, payload) - {:hkdf,hash} -> CA.HKDF.derive({:kdf,hash}, sharedKey, 32, payload) - end - unwrap = CA.AES.keyUnwrap(encryptedKey, derived) - res = CA.AES.decrypt(enc, data, unwrap, iv) - {:ok, res} - end - - # CMS Codec KTRI: RSA+RSAES-OAEP - - def ktri(ktri, privateKeyBin, encOID, data, iv) do - {:'KeyTransRecipientInfo',_vsn,_,{_,schemeOID,_},key} = ktri - {:rsaEncryption,_} = CA.ALG.lookup schemeOID - {enc,_} = CA.ALG.lookup(encOID) - sessionKey = :public_key.decrypt_private(key, privateKeyBin) - res = CA.AES.decrypt(enc, data, sessionKey, iv) - {:ok, res} - end - - # CMS Codec KEKRI: KEK+AES-KW+CBC - - def kekri(kekri, privateKeyBin, encOID, data, iv) do - {:'KEKRecipientInfo',_vsn,_,{_,kea,_},encryptedKey} = kekri - _ = CA.ALG.lookup(kea) - {enc,_} = CA.ALG.lookup(encOID) - unwrap = CA.AES.keyUnwrap(encryptedKey,privateKeyBin) - res = CA.AES.decrypt(enc, data, unwrap, iv) - {:ok, res} - end - - # CMS DECRYPT API - - def decrypt(cms, {schemeOID, privateKeyBin}) do - {:ok,{:ContentInfo,_,{:EnvelopedData,_,_,x,y,_}}} = cms - {:EncryptedContentInfo,_,{_,encOID,{_,<<_::16,iv::binary>>}},data} = y - case :proplists.get_value(:kari, x, []) do - [] -> case :proplists.get_value(:ktri, x, []) do - [] -> case :proplists.get_value(:kekri, x, []) do - [] -> case :proplists.get_value(:pwri, x, []) do - [] -> {:error, "Unknown Other Recepient Info"} - pwri -> pwri(pwri, privateKeyBin, encOID, data, iv) end - kekri -> kekri(kekri, privateKeyBin, encOID, data, iv) end - ktri -> ktri(ktri, privateKeyBin, encOID, data, iv) end - kari -> kari(kari, privateKeyBin, schemeOID, encOID, data, iv) - end - end - - # CMS Codec PWRI: PBKDF2+AES-KW+CBC - - def pwri(pwri, privateKeyBin, encOID, data, iv) do - {:error, ["PWRI not implemented", pwri, privateKeyBin, encOID, data, iv]} - end - - # Test - - def pem(name), do: hd(:public_key.pem_decode(:erlang.element(2,:file.read_file(name)))) - - def testDecryptECC(), do: CA.CMS.decrypt(testECC(), testPrivateKeyECC()) - def testDecryptKEK(), do: CA.CMS.decrypt(testKEK(), testPrivateKeyKEK()) - def testDecryptRSA(), do: CA.CMS.decrypt(testRSA(), testPrivateKeyRSA()) - def test(), do: - [ - testDecryptECC(), - testDecryptKEK(), - testDecryptRSA(), - testCMS(), - ] - - def testPrivateKeyECC() do - privateKey = :public_key.pem_entry_decode(pem("priv/certs/client.key")) - {:'ECPrivateKey',_,privateKeyBin,{:namedCurve,schemeOID},_,_} = privateKey - {schemeOID,privateKeyBin} - end - - def testPrivateKeyKEK() do - {:kek, :binary.decode_hex("0123456789ABCDEF0123456789ABCDEF")} - end - - def testPrivateKeyRSA() do - {:ok,bin} = :file.read_file("priv/rsa-cms.key") - pki = :public_key.pem_decode(bin) - [{:PrivateKeyInfo,_,_}] = pki - rsa = :public_key.pem_entry_decode(hd(pki)) - {:'RSAPrivateKey',:'two-prime',_n,_e,_d,_,_,_,_,_,_} = rsa - {:rsaEncryption,rsa} - end - - def testECC() do - {:ok,base} = :file.read_file "priv/certs/encrypted.txt" - [_,s] = :string.split base, "\n\n" - x = :base64.decode s - :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) - end - - def testKEK() do - {:ok,base} = :file.read_file "priv/certs/encrypted2.txt" - [_,s] = :string.split base, "\n\n" - x = :base64.decode s - :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) - end - - def testRSA() do - {:ok,x} = :file.read_file "priv/rsa-cms.bin" - :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) - end - - def testCMS() do - privateKey = :erlang.element(3,:public_key.pem_entry_decode(pem("priv/certs/client.key"))) - scheme = :secp384r1 - {_,{:ContentInfo,_,{:EnvelopedData,_,_,x,{_,_,{_,_,{_,<<_::16,iv::binary>>}},data},_}}} = testECC() - [{:kari,{_,:v3,{_,{_,_,publicKey}},ukm,_,[{_,_,encryptedKey}]}}|_] = x - sharedKey = :crypto.compute_key(:ecdh,publicKey,privateKey,scheme) - {_,content} = :'CMSECCAlgs-2009-02'.encode(:'ECC-CMS-SharedInfo', CA.CMS.sharedInfo(ukm,256)) - kdf = CA.KDF.derive({:kdf, :sha256}, sharedKey, 32, content) - unwrap = :aes_kw.unwrap(encryptedKey, kdf) - CA.AES.decrypt(:'id-aes256-CBC', data, unwrap, iv) - end + @moduledoc "CA/CMS library." + + def oid(:"id-cms-data"), do: {1,2,840,113549,1,7,1} + def oid(:"id-cms-signedData"), do: {1,2,840,113549,1,7,2} + def oid(:"id-cms-envelopedData"), do: {1,2,840,113549,1,7,3} + def oid(:"id-cms-digestedData"), do: {1,2,840,113549,1,7,4} + def oid(:"id-cms-encryptedData"), do: {1,2,840,113549,1,7,5} + + def contentInfo(cms) do {:ok, ci} = :"CryptographicMessageSyntax-2010".decode :ContentInfo, cms ; ci end + + def map(:'dhSinglePass-stdDH-sha512kdf-scheme'), do: {:kdf, :sha512} + def map(:'dhSinglePass-stdDH-sha384kdf-scheme'), do: {:kdf, :sha384} + def map(:'dhSinglePass-stdDH-sha256kdf-scheme'), do: {:kdf, :sha256} + def map(:'dhSinglePass-stdDH-hkdf-sha256-scheme'), do: {:hkdf, :sha256} + def map(:'dhSinglePass-stdDH-hkdf-sha384-scheme'), do: {:hkdf, :sha384} + def map(:'dhSinglePass-stdDH-hkdf-sha512-scheme'), do: {:hkdf, :sha512} + + def sharedInfo(ukm, len), do: {:'ECC-CMS-SharedInfo', + {:'KeyWrapAlgorithm',{2,16,840,1,101,3,4,1,45},:asn1_NOVALUE}, ukm, <>} + + # CMS Codec KARI: ECC+KDF/ECB+AES/KW+256/CBC + + def kari(kari, privateKeyBin, schemeOID, encOID, data, iv) do + {:'KeyAgreeRecipientInfo',:v3,{_,{_,_,publicKey}},ukm,{_,kdfOID,_},[{_,_,encryptedKey}]} = kari + {scheme,_} = CA.ALG.lookup(schemeOID) + {kdf,_} = CA.ALG.lookup(kdfOID) + {enc,_} = CA.ALG.lookup(encOID) + sharedKey = :crypto.compute_key(:ecdh,publicKey,privateKeyBin,scheme) + {_,payload} = :'CMSECCAlgs-2009-02'.encode(:'ECC-CMS-SharedInfo', sharedInfo(ukm,256)) + derived = case map(kdf) do + {:kdf,hash} -> CA.KDF.derive({:kdf,hash}, sharedKey, 32, payload) + {:hkdf,hash} -> CA.HKDF.derive({:kdf,hash}, sharedKey, 32, payload) + end + unwrap = CA.AES.keyUnwrap(encryptedKey, derived) + res = CA.AES.decrypt(enc, data, unwrap, iv) + {:ok, res} + end + + # CMS Codec KTRI: RSA+RSAES-OAEP + + def ktri(ktri, privateKeyBin, encOID, data, iv) do + {:'KeyTransRecipientInfo',_vsn,_,{_,schemeOID,_},key} = ktri + {:rsaEncryption,_} = CA.ALG.lookup schemeOID + {enc,_} = CA.ALG.lookup(encOID) + sessionKey = :public_key.decrypt_private(key, privateKeyBin) + res = CA.AES.decrypt(enc, data, sessionKey, iv) + {:ok, res} + end + + # CMS Codec KEKRI: KEK+AES-KW+CBC + + def kekri(kekri, privateKeyBin, encOID, data, iv) do + {:'KEKRecipientInfo',_vsn,_,{_,kea,_},encryptedKey} = kekri + _ = CA.ALG.lookup(kea) + {enc,_} = CA.ALG.lookup(encOID) + unwrap = CA.AES.keyUnwrap(encryptedKey,privateKeyBin) + res = CA.AES.decrypt(enc, data, unwrap, iv) + {:ok, res} + end + + # CMS DECRYPT API + + def decrypt(cms, {schemeOID, privateKeyBin}) do + {:ok,{:ContentInfo,_,{:EnvelopedData,_,_,x,y,_}}} = cms + {:EncryptedContentInfo,_,{_,encOID,{_,<<_::16,iv::binary>>}},data} = y + case :proplists.get_value(:kari, x, []) do + [] -> case :proplists.get_value(:ktri, x, []) do + [] -> case :proplists.get_value(:kekri, x, []) do + [] -> case :proplists.get_value(:pwri, x, []) do + [] -> {:error, "Unknown Other Recepient Info"} + pwri -> pwri(pwri, privateKeyBin, encOID, data, iv) end + kekri -> kekri(kekri, privateKeyBin, encOID, data, iv) end + ktri -> ktri(ktri, privateKeyBin, encOID, data, iv) end + kari -> kari(kari, privateKeyBin, schemeOID, encOID, data, iv) + end + end + + # CMS Codec PWRI: PBKDF2+AES-KW+CBC + + def pwri(pwri, privateKeyBin, encOID, data, iv) do + {:error, ["PWRI not implemented", pwri, privateKeyBin, encOID, data, iv]} + end + + # Test + + def pem(name), do: hd(:public_key.pem_decode(:erlang.element(2,:file.read_file(name)))) + def testDecryptECC(), do: CA.CMS.decrypt(testECC(), testPrivateKeyECC()) + def testDecryptKEK(), do: CA.CMS.decrypt(testKEK(), testPrivateKeyKEK()) + def testDecryptRSA(), do: CA.CMS.decrypt(testRSA(), testPrivateKeyRSA()) + def test(), do: + [ + testDecryptECC(), + testDecryptKEK(), + testDecryptRSA(), + testCMS(), + ] + + def testPrivateKeyECC() do + privateKey = :public_key.pem_entry_decode(pem("priv/certs/client.key")) + {:'ECPrivateKey',_,privateKeyBin,{:namedCurve,schemeOID},_,_} = privateKey + {schemeOID,privateKeyBin} + end + + def testPrivateKeyKEK() do + {:kek, :binary.decode_hex("0123456789ABCDEF0123456789ABCDEF")} + end + + def testPrivateKeyRSA() do + {:ok,bin} = :file.read_file("priv/rsa-cms.key") + pki = :public_key.pem_decode(bin) + [{:PrivateKeyInfo,_,_}] = pki + rsa = :public_key.pem_entry_decode(hd(pki)) + {:'RSAPrivateKey',:'two-prime',_n,_e,_d,_,_,_,_,_,_} = rsa + {:rsaEncryption,rsa} + end + + def testECC() do + {:ok,base} = :file.read_file "priv/certs/encrypted.txt" + [_,s] = :string.split base, "\n\n" + x = :base64.decode s + :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) + end + + def testKEK() do + {:ok,base} = :file.read_file "priv/certs/encrypted2.txt" + [_,s] = :string.split base, "\n\n" + x = :base64.decode s + :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) + end + + def testRSA() do + {:ok,x} = :file.read_file "priv/rsa-cms.bin" + :'CryptographicMessageSyntax-2010'.decode(:ContentInfo, x) + end + + def testCMS() do + privateKey = :erlang.element(3,:public_key.pem_entry_decode(pem("priv/certs/client.key"))) + scheme = :secp384r1 + {:ok,{:ContentInfo,_,{:EnvelopedData,_,_,x,{_,_,{_,_,{_,<<_::16,iv::binary>>}},data},_}}} = testECC() + [{:kari,{_,:v3,{_,{_,_,publicKey}},ukm,_,[{_,_,encryptedKey}]}}|_] = x + sharedKey = :crypto.compute_key(:ecdh,publicKey,privateKey,scheme) + {_,content} = :'CMSECCAlgs-2009-02'.encode(:'ECC-CMS-SharedInfo', CA.CMS.sharedInfo(ukm,256)) + kdf = CA.KDF.derive({:kdf, :sha256}, sharedKey, 32, content) + unwrap = :aes_kw.unwrap(encryptedKey, kdf) + CA.AES.decrypt(:'id-aes256-CBC', data, unwrap, iv) + end end \ No newline at end of file diff --git a/lib/oid/cmc-oid.ex b/lib/oid/cmc-oid.ex deleted file mode 100644 index a98a2b4..0000000 --- a/lib/oid/cmc-oid.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule CA.CMC.AT do - @moduledoc "CA CMC OIDs." - def oid(:"id-cmc-identification"), do: {1,3,6,1,5,5,7,7,2} - def oid(:"id-cmc-identityProof"), do: {1,3,6,1,5,5,7,7,3} - def oid(:"id-cmc-dataReturn"), do: {1,3,6,1,5,5,7,7,4} - def oid(:"id-cmc-transactionId"), do: {1,3,6,1,5,5,7,7,5} - def oid(:"id-cmc-senderNonce"), do: {1,3,6,1,5,5,7,7,6} - def oid(:"id-cmc-recipientNonce"), do: {1,3,6,1,5,5,7,7,7} - def oid(:"id-cmc-statusInfo"), do: {1,3,6,1,5,5,7,7,1} - def oid(:"id-cmc-addExtensions"), do: {1,3,6,1,5,5,7,7,8} - def oid(:"id-cmc-encryptedPOP"), do: {1,3,6,1,5,5,7,7,9} - def oid(:"id-cmc-decryptedPOP"), do: {1,3,6,1,5,5,7,7,10} - def oid(:"id-cmc-lraPOPWitness"), do: {1,3,6,1,5,5,7,7,11} - def oid(:"id-cmc-getCert"), do: {1,3,6,1,5,5,7,7,15} - def oid(:"id-cmc-getCRL"), do: {1,3,6,1,5,5,7,7,16} - def oid(:"id-cmc-revokeRequest"), do: {1,3,6,1,5,5,7,7,17} - def oid(:"id-cmc-regInfo"), do: {1,3,6,1,5,5,7,7,18} - def oid(:"id-cmc-responseInfo"), do: {1,3,6,1,5,5,7,7,19} - def oid(:"id-cmc-queryPending"), do: {1,3,6,1,5,5,7,7,21} - def oid(:"id-cmc-popLinkRandom"), do: {1,3,6,1,5,5,7,7,22} - def oid(:"id-cmc-popLinkWitness"), do: {1,3,6,1,5,5,7,7,23} - def oid(:"id-cmc-confirmCertAcceptance"), do: {1,3,6,1,5,5,7,7,24} - def oid(:"id-cmc-statusInfoV2"), do: {1,3,6,1,5,5,7,7,25} - def oid(:"id-cmc-trustedAnchors"), do: {1,3,6,1,5,5,7,7,26} - def oid(:"id-cmc-authData"), do: {1,3,6,1,5,5,7,7,27} - def oid(:"id-cmc-batchRequests"), do: {1,3,6,1,5,5,7,7,28} - def oid(:"id-cmc-batchResponses"), do: {1,3,6,1,5,5,7,7,29} - def oid(:"id-cmc-publishCert"), do: {1,3,6,1,5,5,7,7,30} - def oid(:"id-cmc-modCertTemplate"), do: {1,3,6,1,5,5,7,7,31} - def oid(:"id-cmc-controlProcessed"), do: {1,3,6,1,5,5,7,7,32} - def oid(:"id-cmc-identityProofV2"), do: {1,3,6,1,5,5,7,7,33} - def oid(:"id-cmc-popLinkWitnessV2"), do: {1,3,6,1,5,5,7,7,34} -end diff --git a/lib/services/cmc.ex b/lib/services/cmc.ex index d50740b..27cc530 100644 --- a/lib/services/cmc.ex +++ b/lib/services/cmc.ex @@ -1,39 +1,71 @@ defmodule CA.CMC do - @moduledoc "CA/CMC TLS TCP server." - require CA - - def code(), do: :binary.encode_hex(:crypto.strong_rand_bytes(8)) - def start(), do: {:ok, :erlang.spawn(fn -> listen(1839) end)} - - def listen(port) do - {:ok, socket} = :gen_tcp.listen(port, - [:binary, {:packet, 0}, {:active, false}, {:reuseaddr, true}]) - accept(socket) - end - - def accept(socket) do - {:ok, fd} = :gen_tcp.accept(socket) - :erlang.spawn(fn -> __MODULE__.loop(fd) end) - accept(socket) - end - - def message(_socket, cms) do - :logger.info 'Unknown message request ~p', [cms] - end - - def answer(socket, res) do - :gen_tcp.send(socket, res) - end - - def loop(socket) do - case :gen_tcp.recv(socket, 0) do - {:ok, data} -> - {:ok, dec} = :'EnrollmentMessageSyntax-2009'.decode(:'PKIData', data) - {:PKIData, _controlSequence, _reqSequence, cmsSequence, _otherMsgSequence} = dec - :io.format 'PKIData:~n~p~n', [dec] - __MODULE__.message(socket, cmsSequence) - loop(socket) - {:error, :closed} -> :exit - end - end -end \ No newline at end of file + @moduledoc "CA/CMC TLS TCP server." + require CA + + def oid(:"id-cmc-identification"), do: {1,3,6,1,5,5,7,7,2} + def oid(:"id-cmc-identityProof"), do: {1,3,6,1,5,5,7,7,3} + def oid(:"id-cmc-dataReturn"), do: {1,3,6,1,5,5,7,7,4} + def oid(:"id-cmc-transactionId"), do: {1,3,6,1,5,5,7,7,5} + def oid(:"id-cmc-senderNonce"), do: {1,3,6,1,5,5,7,7,6} + def oid(:"id-cmc-recipientNonce"), do: {1,3,6,1,5,5,7,7,7} + def oid(:"id-cmc-statusInfo"), do: {1,3,6,1,5,5,7,7,1} + def oid(:"id-cmc-addExtensions"), do: {1,3,6,1,5,5,7,7,8} + def oid(:"id-cmc-encryptedPOP"), do: {1,3,6,1,5,5,7,7,9} + def oid(:"id-cmc-decryptedPOP"), do: {1,3,6,1,5,5,7,7,10} + def oid(:"id-cmc-lraPOPWitness"), do: {1,3,6,1,5,5,7,7,11} + def oid(:"id-cmc-getCert"), do: {1,3,6,1,5,5,7,7,15} + def oid(:"id-cmc-getCRL"), do: {1,3,6,1,5,5,7,7,16} + def oid(:"id-cmc-revokeRequest"), do: {1,3,6,1,5,5,7,7,17} + def oid(:"id-cmc-regInfo"), do: {1,3,6,1,5,5,7,7,18} + def oid(:"id-cmc-responseInfo"), do: {1,3,6,1,5,5,7,7,19} + def oid(:"id-cmc-queryPending"), do: {1,3,6,1,5,5,7,7,21} + def oid(:"id-cmc-popLinkRandom"), do: {1,3,6,1,5,5,7,7,22} + def oid(:"id-cmc-popLinkWitness"), do: {1,3,6,1,5,5,7,7,23} + def oid(:"id-cmc-confirmCertAcceptance"), do: {1,3,6,1,5,5,7,7,24} + def oid(:"id-cmc-statusInfoV2"), do: {1,3,6,1,5,5,7,7,25} + def oid(:"id-cmc-trustedAnchors"), do: {1,3,6,1,5,5,7,7,26} + def oid(:"id-cmc-authData"), do: {1,3,6,1,5,5,7,7,27} + def oid(:"id-cmc-batchRequests"), do: {1,3,6,1,5,5,7,7,28} + def oid(:"id-cmc-batchResponses"), do: {1,3,6,1,5,5,7,7,29} + def oid(:"id-cmc-publishCert"), do: {1,3,6,1,5,5,7,7,30} + def oid(:"id-cmc-modCertTemplate"), do: {1,3,6,1,5,5,7,7,31} + def oid(:"id-cmc-controlProcessed"), do: {1,3,6,1,5,5,7,7,32} + def oid(:"id-cmc-identityProofV2"), do: {1,3,6,1,5,5,7,7,33} + def oid(:"id-cmc-popLinkWitnessV2"), do: {1,3,6,1,5,5,7,7,34} + + def code(), do: :binary.encode_hex(:crypto.strong_rand_bytes(8)) + def start(), do: {:ok, :erlang.spawn(fn -> listen(1839) end)} + + def listen(port) do + {:ok, socket} = :gen_tcp.listen(port, + [:binary, {:packet, 0}, {:active, false}, {:reuseaddr, true}]) + accept(socket) + end + + def accept(socket) do + {:ok, fd} = :gen_tcp.accept(socket) + :erlang.spawn(fn -> __MODULE__.loop(fd) end) + accept(socket) + end + + def message(_socket, cms) do + :logger.info 'Unknown message request ~p', [cms] + end + + def answer(socket, res) do + :gen_tcp.send(socket, res) + end + + def loop(socket) do + case :gen_tcp.recv(socket, 0) do + {:ok, data} -> + {:ok, dec} = :'EnrollmentMessageSyntax-2009'.decode(:'PKIData', data) + {:PKIData, _controlSequence, _reqSequence, cmsSequence, _otherMsgSequence} = dec + :io.format 'PKIData:~n~p~n', [dec] + __MODULE__.message(socket, cmsSequence) + loop(socket) + {:error, :closed} -> :exit + end + end + +end diff --git a/lib/services/csr.ex b/lib/services/csr.ex index e2bef04..125f4a2 100644 --- a/lib/services/csr.ex +++ b/lib/services/csr.ex @@ -1,6 +1,8 @@ defmodule CA.CSR do @moduledoc "CA/CSR library." + # TODO: pass CSR Attrs + def ca() do ca_key = X509.PrivateKey.new_ec(:secp384r1) dn = "/C=UA/L=Київ/O=SYNRC/CN=CA" diff --git a/lib/services/est.ex b/lib/services/est.ex index 58b6414..88f9f33 100644 --- a/lib/services/est.ex +++ b/lib/services/est.ex @@ -20,17 +20,14 @@ defmodule CA.EST do # Authority PKI X.509 EST RFC 7030 3.2.2 - get "/.well-known/est/cacerts" do CA.EST.Get.get(conn, [], "Authority", [], "CONTENT") end + get "/.well-known/est/ca" do CA.EST.Get.get(conn, [], "Authority", [], "CA") end + get "/.well-known/est/cacerts" do CA.EST.Get.get(conn, [], "Authority", [], "CMS") end get "/.well-known/est/csrattrs" do CA.EST.Get.get(conn, [], "Authority", [], "ABAC") end post "/.well-known/est/simpleenroll" do CA.EST.Post.post(conn, [], "Authority", [], "ENROLL") end post "/.well-known/est/simplereenroll" do CA.EST.Post.post(conn, [], "Authority", [], "RE-ENROLL") end post "/.well-known/est/serverkeygen" do CA.EST.Post.post(conn, [], "Authority", [], "KEYGEN") end post "/.well-known/est/fullcmc" do CA.EST.Post.post(conn, [], "Authority", [], "CMC") end - # Authority PKI X.509 EST SYNRC 5.10.4 - - get "/.well-known/est/ca" do CA.EST.Get.get(conn, [], "Authority", [], "CA") end - # See Page 36 of RFC 7030 # [1] https://www.rfc-editor.org/rfc/rfc7030 # [2] https://www.ietf.org/archive/id/draft-ietf-lamps-rfc7030-csrattrs-07.html diff --git a/lib/services/http/get.ex b/lib/services/http/get.ex index 42a6c3e..4f8d41b 100644 --- a/lib/services/http/get.ex +++ b/lib/services/http/get.ex @@ -1,5 +1,6 @@ defmodule CA.EST.Get do import Plug.Conn + def get(conn, [], "Authority", [], "CA") do body = :base64.encode(CA.CSR.read_ca_public()) conn |> put_resp_content_type("application/pkcs7-mime") @@ -8,6 +9,23 @@ defmodule CA.EST.Get do |> resp(200, body) |> send_resp() end + + def get(conn, [], "Authority", [], "CMS") do + ca = CA.CSR.read_ca_public() + {:ok, cacert} = :"PKIX1Explicit-2009".decode(:Certificate, ca) + ci = {:ContentInfo, {1, 2, 840, 113549, 1, 7, 2}, + {:SignedData, :v1, [], + {:EncapsulatedContentInfo, {1, 2, 840, 113549, 1, 7, 1}, :asn1_NOVALUE}, + [certificate: cacert], [], []}} + {:ok, cms} = :"CryptographicMessageSyntax-2010".encode :ContentInfo, ci + body = :base64.encode cms + conn |> put_resp_content_type("application/pkcs7-mime") + |> put_resp_header("Content-Transfer-Encoding", "base64") + |> put_resp_header("Content-Length", :erlang.integer_to_binary(:erlang.size(body))) + |> resp(200, body) + |> send_resp() + end + def get(conn, [], "Authority", [], "ABAC") do body = :base64.encode(CA.EST.csrattributes()) conn |> put_resp_content_type("application/csrattrs") @@ -16,8 +34,10 @@ defmodule CA.EST.Get do |> resp(200, body) |> send_resp() end + def get(conn, _, type, id, spec) do :io.format 'GET/4:#{type}/#{id}/#{spec}', [] send_resp(conn, 200, CA.EST.encode([%{"type" => type, "id" => id, "spec" => spec}])) end + end \ No newline at end of file diff --git a/lib/signing/CAdES.ex b/lib/signing/CAdES.ex index 204d27f..9d076c0 100644 --- a/lib/signing/CAdES.ex +++ b/lib/signing/CAdES.ex @@ -86,6 +86,17 @@ defmodule CA.CAdES do def flat(code,k,acc) when is_list(k), do: [:lists.map(fn x -> flat(code,x,acc) end, k)|acc] def flat(_code,k,acc) when is_binary(k), do: [k|acc] + def parseCert(cert) do + {:Certificate, a, _, _} = cert + {:Certificate_toBeSigned, _ver, _sel, _alg, issuer, _val, issuee, _a, _b, _c, exts} = a + extensions = :lists.map(fn {:Extension,code,_x,b} -> + oid(code, :lists.flatten(flat(code,:asn1rt_nif.decode_ber_tlv(b),[]))) + end, exts) + person = :lists.flatten(:erlang.element(2, issuee)) + ca = :lists.flatten(:erlang.element(2, issuer)) + [parseAttrs(person,extensions),parseAttrs(ca,[])] + end + def parseAttrs(attrs, options) do certinfo( o: extract({2, 5, 4, 10}, attrs), @@ -117,16 +128,6 @@ defmodule CA.CAdES do [parseAttrs(person,extensions),parseAttrs(ca,attributes)] end - def parseCert(cert) do - {:Certificate, a, _, _} = cert - {:Certificate_toBeSigned, _ver, _sel, _alg, issuer, _val, issuee, _a, _b, _c, exts} = a - extensions = :lists.map(fn {:Extension,code,_x,b} -> - oid(code, :lists.flatten(flat(code,:asn1rt_nif.decode_ber_tlv(b),[]))) - end, exts) - person = :lists.flatten(:erlang.element(2, issuee)) - ca = :lists.flatten(:erlang.element(2, issuer)) - [parseAttrs(person,extensions),parseAttrs(ca,[])] - end def parseSignData(bin) do {_, {:ContentInfo, oid, ci}} = :KEP.decode(:ContentInfo, bin)