%%% Copyright (c) 2014, NORDUnet A/S. %%% See LICENSE for licensing information. -module(x509). -export([valid_chain_p/3, detox_precert/1]). -include_lib("public_key/include/public_key.hrl"). -type der_encoded() :: public_key:der_encoded(). %%%%%%%%%%%%%%%%%%%% %% @doc Verify that the leaf cert or precert has a valid chain back to %% an acceptable root cert. Order of certificates in second argument %% is: leaf cert in head, chain in tail. Order of first argument is %% irrelevant. -spec valid_chain_p([der_encoded()], [der_encoded()], integer()) -> boolean(). valid_chain_p(_, _, MaxChainLength) when MaxChainLength =< 0 -> false; valid_chain_p(AcceptableRootCerts, [TopCert], MaxChainLength) -> case lists:member(TopCert, AcceptableRootCerts) of true -> % Top cert is part of chain. true; false -> % Top cert might be signed by cert in truststore. (MaxChainLength > 1) and lists:any(fun(X) -> signed_by_p(TopCert, X) end, AcceptableRootCerts) end; valid_chain_p(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> case signed_by_p(BottomCert, hd(Rest)) of false -> false; true -> valid_chain_p(AcceptableRootCerts, Rest, MaxChainLength - 1) end. -spec signed_by_p(der_encoded(), der_encoded()) -> boolean(). signed_by_p(Cert, IssuerCert) -> %% FIXME: Validate presence and contents (against constraints) of %% names (subject, subjectAltName, emailAddress) too? case public_key:pkix_is_issuer(Cert, IssuerCert) of true -> % Cert.issuer does match IssuerCert.subject. public_key:pkix_verify(Cert, public_key(IssuerCert)); false -> false end. -spec public_key(der_encoded() | #'OTPCertificate'{}) -> public_key:public_key(). public_key(CertDer) when is_binary(CertDer) -> public_key(public_key:pkix_decode_cert(CertDer, otp)); public_key(#'OTPCertificate'{ tbsCertificate = #'OTPTBSCertificate'{subjectPublicKeyInfo = #'OTPSubjectPublicKeyInfo'{ subjectPublicKey = Key}}}) -> Key. %%%%%%%%%%%%%%%%%%%% %% Precertificates according to draft-ietf-trans-rfc6962-bis-04. %% Submitted precerts have a special critical poison extension -- OID %% 1.3.6.1.4.1.11129.2.4.3, whose extnValue OCTET STRING contains %% ASN.1 NULL data (0x05 0x00). %% They are signed with either the CA cert that will sign the final %% cert or Precertificate Signing Certificate directly signed by the %% CA cert that will sign the final cert. A Precertificate Signing %% Certificate has CA:true and Extended Key Usage: Certificate %% Transparency, OID 1.3.6.1.4.1.11129.2.4.4. %% A PreCert in a SignedCertificateTimestamp does _not_ contain the %% poison extension, nor a Precertificate Signing Certificate. This %% means that we might have to 1) remove poison extensions in leaf %% certs, 2) remove "poisoned signatures", 3) change issuer and %% Authority Key Identifier of leaf certs. -spec detox_precert([#'Certificate'{}]) -> [#'Certificate'{}]. detox_precert(CertChain) -> CertChain. % NYI