diff options
-rw-r--r-- | src/v1.erl | 10 | ||||
-rw-r--r-- | src/x509.erl | 39 |
2 files changed, 31 insertions, 18 deletions
@@ -21,13 +21,13 @@ request(post, "ct/v1/add-chain", Input) -> Roots = catlfish:known_roots(), case x509:normalise_chain(Roots, [LeafCert|CertChain]) of {ok, [Leaf | Chain]} -> - io:format("[info] adding ~p~n", + io:format("[info] adding ~p~n", [x509:cert_string(LeafCert)]), success(catlfish:add_chain(Leaf, Chain)); - {Err, Msg} -> - io:format("[info] rejecting ~p: ~p~n", - [x509:cert_string(LeafCert), Err]), - html("add-chain: ", [Msg, Err]) + {error, Reason} -> + io:format("[info] rejecting ~p: ~p~n", + [x509:cert_string(LeafCert), Reason]), + html("add-chain: invalid chain", Reason) end; Invalid -> html("add-chain: chain is not a list: ", [Invalid]) diff --git a/src/x509.erl b/src/x509.erl index 9b6b386..a784354 100644 --- a/src/x509.erl +++ b/src/x509.erl @@ -6,15 +6,20 @@ -include_lib("public_key/include/public_key.hrl"). --type reason() :: {chain_too_long | root_unknown | chain_broken}. +-type reason() :: {chain_too_long | + root_unknown | + chain_broken | + signature_mismatch | + encoding_invalid}. -define(MAX_CHAIN_LENGTH, 10). --spec normalise_chain([binary()], [binary()]) -> [binary()]. +-spec normalise_chain([binary()], [binary()]) -> {ok, [binary()]} | + {error, reason()}. normalise_chain(AcceptableRootCerts, CertChain) -> case valid_chain_p(AcceptableRootCerts, CertChain, ?MAX_CHAIN_LENGTH) of {false, Reason} -> - {Reason, "invalid chain"}; + {error, Reason}; {true, Root} -> [Leaf | Chain] = CertChain, {ok, [detox_precert(Leaf) | Chain] ++ Root} @@ -49,29 +54,37 @@ valid_chain_p(AcceptableRootCerts, [TopCert], MaxChainLength) -> end; valid_chain_p(AcceptableRootCerts, [BottomCert|Rest], MaxChainLength) -> case signed_by_p(BottomCert, hd(Rest)) of - false -> {false, chain_broken}; - true -> valid_chain_p(AcceptableRootCerts, Rest, MaxChainLength - 1) + true -> valid_chain_p(AcceptableRootCerts, Rest, MaxChainLength - 1); + Err -> Err end. -%% @doc Return list with first --spec signer(binary(), [binary()]) -> list(). +%% @doc Return first cert in list signing Cert, or notfound. +-spec signer(binary(), [binary()]) -> notfound | binary(). signer(_Cert, []) -> notfound; signer(Cert, [H|T]) -> case signed_by_p(Cert, H) of true -> H; - false -> signer(Cert, T) + {false, _} -> signer(Cert, T) end. --spec signed_by_p(binary(), binary()) -> boolean(). +-spec signed_by_p(binary(), binary()) -> true | {false, reason()}. 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)); + case (catch public_key:pkix_is_issuer(Cert, IssuerCert)) of + {'EXIT', _Reason} -> + %% Invalid ASN.1. + {false, encoding_invalid}; + true -> + %% Cert.issuer does match IssuerCert.subject. Now verify + %% the signature. + case public_key:pkix_verify(Cert, public_key(IssuerCert)) of + true -> true; + false -> {false, signature_mismatch} + end; false -> - false + {false, chain_broken} end. -spec public_key(binary() | #'OTPCertificate'{}) -> public_key:public_key(). |