defmodule CA.CMP do @moduledoc "CA/CMP TCP server." require CA def parseSubj(csr) do {:CertificationRequest, {:CertificationRequestInfo, v, subj, x, y}, b, c} = csr {:CertificationRequest, {:CertificationRequestInfo, v, CA.CRT.subj(subj), x, y}, b, c} end def convertOTPtoPKIX(cert) do {:Certificate,{:TBSCertificate,:v3,a,ai,rdn,v,rdn2,{p1,{p21,p22,_pki},p3},b,c,ext},ai,code} = :public_key.pkix_decode_cert(:public_key.pkix_encode(:OTPCertificate, cert, :otp), :plain) {:Certificate,{:TBSCertificate,:v3,a,ai,CA.CRT.unsubj(rdn),v,CA.CRT.unsubj(rdn2), {p1,{p21,p22,{:namedCurve,{1,3,132,0,34}}},p3},b,c,ext},ai,code} end def start(), do: {:ok, :erlang.spawn(fn -> listen(1829) 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 loop(socket) do case :gen_tcp.recv(socket, 0) do {:ok, data} -> [_headers,body] = :string.split data, "\r\n\r\n", :all {:ok,dec} = :'PKIXCMP-2009'.decode(:'PKIMessage', body) {:PKIMessage, header, body, code, _extra} = dec __MODULE__.message(socket, header, body, code) loop(socket) {:error, :closed} -> :exit end end def baseKey(pass, salt, iter, owf \\ :sha256), do: :lists.foldl(fn _, acc -> :crypto.hash(owf, acc) end, pass <> salt, :lists.seq(1,iter)) def protection(:asn1_NOVALUE), do: {"","","","",1} def protection(protectionAlg) do {_,oid,{_,param}} = protectionAlg {:ok, parameters} = :"PKIXCMP-2009".decode(:'PBMParameter', param) {:PBMParameter, salt, {_,owf,_}, counter, {_,mac,_} } = parameters {oid, salt, owf, mac, counter} end def validateProtection(header, body, code) do {:PKIHeader, _, _, _, _, protectionAlg, _, _, _, _, _, _, _} = header {oid, salt, owfoid, macoid, counter} = protection(protectionAlg) case CA.ALG.lookup(oid) do {:'id-PasswordBasedMac', _ } -> incomingProtection = CA."ProtectedPart"(header: header, body: body) {:ok, bin} = :"PKIXCMP-2009".encode(:'ProtectedPart', incomingProtection) {owf,_} = CA.ALG.lookup(owfoid) # SHA-2 pbm = :application.get_env(:ca, :pbm, "0000") # DH shared secret verifyKey = baseKey(pbm, salt, counter, owf) hash = CA.KDF.hs(:erlang.size(code)) case CA.ALG.lookup(macoid) do {:'hMAC-SHA1',_} -> :crypto.mac(:hmac, hash, verifyKey, bin) _ -> :crypto.mac(:hmac, hash, verifyKey, bin) end {_, _ } -> "" end end def answer(socket, header, body, code) do message = CA."PKIMessage"(header: header, body: body, protection: code) {:ok, bytes} = :'PKIXCMP-2009'.encode(:'PKIMessage', message) res = "HTTP/1.0 200 OK\r\n" <> "Server: SYNRC CA/CMP\r\n" <> "Content-Type: application/pkixcmp\r\n\r\n" <> :erlang.iolist_to_binary(bytes) :gen_tcp.send(socket, res) end def message(_socket, _header, {:ir, req}, _) do :lists.map(fn {:CertReqMsg, req, sig, code} -> :logger.info 'request: ~p ~p ~p~n', [req,sig,code] end, req) end def message(_socket, _header, {:genm, req} = _body, _code) do :io.format 'generalMessage: ~p~n', [req] end def message(socket, header, {:p10cr, csr} = body, code) do {:PKIHeader, pvno, from, to, messageTime, protectionAlg, senderKID, _recipKID, transactionID, senderNonce, _recipNonce, _freeText, _generalInfo} = header true = code == validateProtection(header, body, code) :logger.info 'P10CR ~p~n', [senderKID] {ca_key, ca} = CA.CSR.read_ca() subject = X509.CSR.subject(csr) true = X509.CSR.valid?(parseSubj(csr)) cert = X509.Certificate.new(X509.CSR.public_key(csr), CA.CRT.subj(subject), ca, ca_key, extensions: [subject_alt_name: X509.Certificate.Extension.subject_alt_name(["synrc.com"]) ]) reply = CA."CertRepMessage"(response: [ CA."CertResponse"(certReqId: 0, certifiedKeyPair: CA."CertifiedKeyPair"(certOrEncCert: {:certificate, {:x509v3PKCert, convertOTPtoPKIX(cert)}}), status: CA."PKIStatusInfo"(status: 0))]) pkibody = {:cp, reply} pkiheader = CA."PKIHeader"(sender: to, recipient: from, pvno: pvno, recipNonce: senderNonce, transactionID: transactionID, protectionAlg: protectionAlg, messageTime: messageTime) :ok = answer(socket, pkiheader, pkibody, validateProtection(pkiheader, pkibody, code)) :logger.info 'CP ~p~n', [senderNonce] end def message(socket, header, {:certConf, statuses}, code) do {:PKIHeader, _, from, to, _, _, _, _, _, senderNonce, _, _, _} = header :lists.map(fn {:CertStatus,bin,no,{:PKIStatusInfo, :accepted, _, _}} -> :logger.info 'CERTCONF ~p request ~p~n', [no,:binary.part(bin,0,8)] end, statuses) pkibody = {:pkiconf, :asn1_NOVALUE} pkiheader = CA."PKIHeader"(header, sender: to, recipient: from, recipNonce: senderNonce) :ok = answer(socket, pkiheader, pkibody, validateProtection(pkiheader, pkibody, code)) :logger.info 'PKICONF ~p~n', [senderNonce] end def message(_socket, _header, body, _code) do :logger.info 'Strange PKIMessage request ~p', [body] end end