[erlang-questions] Erlang SSL and Invalid Certificate Chains
Ben Murphy
benmmurphy@REDACTED
Mon Aug 11 16:16:47 CEST 2014
(I originally sent this to erlang-bugs but it doesn't look like it
made it there)
We have a problem where an SSL server sends back a certificate chain
that is invalid according to the TLS 1.2 specification and erlang
rejects this chain with an unknown ca error. However, openssl and
browsers will accept this chain because they are less strict about
validation.
The chain looks something like:
0. Server Cert issued by Intermediate Cert
1. Intermediate Cert issued by Root Cert
2. Root Cert issued by Root Cert
3. Unrelated certificate
4. Unrelated certificate
Which is invalid according to: http://www.ietf.org/rfc/rfc5246.txt
certificate_list
This is a sequence (chain) of certificates. The sender's
certificate MUST come first in the list. Each following
certificate MUST directly certify the one preceding it. Because
certificate validation requires that root keys be distributed
independently, the self-signed certificate that specifies the root
certificate authority MAY be omitted from the chain, under the
assumption that the remote end must already possess it in order to
validate it in any case
Looking at the openssl code they start at the beginning of the chain
then recursively find the issuer in order to build up a chain. While
the erlang ssl code assumes the last certificate in the chain is the
root CA (ssl_certificate:trusted_cert_and_path).
Maybe this is more of a feature request than a bug. But I was
wondering if it would be possible for erlang to either accept these
dodgy chains, provide an option when connecting to accept these dodgy
chains or allow users to supply a function to modify the certificate
chain before validation takes place.
I have some code that I've been mucking around with to fix up chains
but I haven't done thorough testing of it.
fix_path(CertChain) ->
DecodedCerts = [ {public_key:pkix_decode_cert(Cert, otp), Cert} ||
Cert <- CertChain ],
[Peer | RestOfChain] = DecodedCerts,
lists:reverse(make_chain(Peer, RestOfChain, [])).
make_chain({OtpCert, Cert}, CertChain, ResultChain) ->
case public_key:pkix_is_self_signed(OtpCert) of
true ->
[Cert | ResultChain ];
false ->
case find_issuer(OtpCert, CertChain) of
{ok, NewCert, NewCertChain} ->
% we remove the cert that was found from the chain
% to prevent infinite loops where a chain becomes
% a loop
make_chain(NewCert, NewCertChain, [Cert | ResultChain]);
{error, issuer_not_found} ->
% assume it is the 'trusted' certificate
[Cert | ResultChain]
end
end.
find_issuer(OtpCert, CertChain) ->
{Not, Maybe} = lists:splitwith(fun({Candidate, _}) ->
not public_key:pkix_is_issuer(OtpCert, Candidate)
end, CertChain),
case Maybe of
[Issuer | Rest] ->
{ok, Issuer, Not ++ Rest};
[] ->
{error, issuer_not_found}
end.
More information about the erlang-questions
mailing list