diff options
author | Ingela Anderton Andin <[email protected]> | 2016-01-13 22:04:00 +0100 |
---|---|---|
committer | Ingela Anderton Andin <[email protected]> | 2018-09-12 10:51:03 +0200 |
commit | 45b6afd71bbb7c4b9a0678067bbb7f9276be5605 (patch) | |
tree | 04935ddb812c53d931bbe3f21d8ff4beff2b6ff6 /lib/ssl/src/ssl_handshake.erl | |
parent | 53b8f2bc723e7a9db8bb01b5c5a2292d12b30b14 (diff) | |
download | otp-45b6afd71bbb7c4b9a0678067bbb7f9276be5605.tar.gz otp-45b6afd71bbb7c4b9a0678067bbb7f9276be5605.tar.bz2 otp-45b6afd71bbb7c4b9a0678067bbb7f9276be5605.zip |
ssl: Handle incomplete and unorded chains
If the peer sends an incomplete chain that we can reconstruct with
our known CA-certs it will be accepted.
We will assume that the peer honors the protocol and sends an orded
chain, however if validation fails we will try to order the chain in
case it was unorded. Will also handle that extraneous cert where present.
See Note form RFC 8446
Note: Prior to TLS 1.3, "certificate_list" ordering required each
certificate to certify the one immediately preceding it; however,
some implementations allowed some flexibility. Servers sometimes
send both a current and deprecated intermediate for transitional
purposes, and others are simply configured incorrectly, but these
cases can nonetheless be validated properly. For maximum
compatibility, all implementations SHOULD be prepared to handle
potentially extraneous certificates and arbitrary orderings from any
TLS version, with the exception of the end-entity certificate which
MUST be first.
Diffstat (limited to 'lib/ssl/src/ssl_handshake.erl')
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 54 |
1 files changed, 45 insertions, 9 deletions
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 3888f9dcf6..dc89fb0029 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -338,7 +338,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, Opts, CRLDbHandle, Role, Host) -> ServerName = server_name(Opts#ssl_options.server_name_indication, Host, Role), - [PeerCert | _] = ASN1Certs, + [PeerCert | ChainCerts ] = ASN1Certs, try {TrustedCert, CertPath} = ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, @@ -347,14 +347,14 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, CertDbHandle, CertDbRef, ServerName, Opts#ssl_options.customize_hostname_check, Opts#ssl_options.crl_check, CRLDbHandle, CertPath), - case public_key:pkix_path_validation(TrustedCert, - CertPath, - [{max_path_length, Opts#ssl_options.depth}, - {verify_fun, ValidationFunAndState}]) of + Options = [{max_path_length, Opts#ssl_options.depth}, + {verify_fun, ValidationFunAndState}], + case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of {ok, {PublicKeyInfo,_}} -> {PeerCert, PublicKeyInfo}; {error, Reason} -> - path_validation_alert(Reason) + handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options, + CertDbHandle, CertDbRef) end catch error:{badmatch,{asn1, Asn1Reason}} -> @@ -363,7 +363,6 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, error:OtherReason -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason}) end. - %%-------------------------------------------------------------------- -spec certificate_verify(binary(), public_key_info(), ssl_record:ssl_version(), term(), binary(), ssl_handshake_history()) -> valid | #alert{}. @@ -1312,6 +1311,45 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath) {unknown, {SslState, UserState}} end. +handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain, + Opts, Options, CertDbHandle, CertsDbRef) -> + handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason); +handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0, + Opts, Options, CertDbHandle, CertsDbRef) -> + case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef, Chain0) of + {ok, _, [PeerCert | Chain] = OrdedChain} when Chain =/= Chain0 -> %% Chain appaears to be unorded + {Trusted, Path} = ssl_certificate:trusted_cert_and_path(OrdedChain, + CertDbHandle, CertsDbRef, + Opts#ssl_options.partial_chain), + case public_key:pkix_path_validation(Trusted, Path, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, PathError} -> + handle_path_validation_error(PathError, PeerCert, Path, + Opts, Options, CertDbHandle, CertsDbRef) + end; + _ -> + path_validation_alert(Reason) + end; +handle_path_validation_error(Reason, _, _, _, _,_, _) -> + path_validation_alert(Reason). + +handle_incomplete_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, PathError0) -> + case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of + {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found + {Trusted, Path} = ssl_certificate:trusted_cert_and_path(Chain, + CertDbHandle, CertsDbRef, + Opts#ssl_options.partial_chain), + case public_key:pkix_path_validation(Trusted, Path, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, PathError} -> + path_validation_alert(PathError) + end; + _ -> + path_validation_alert(PathError0) + end. + path_validation_alert({bad_cert, cert_expired}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); path_validation_alert({bad_cert, invalid_issuer}) -> @@ -1324,8 +1362,6 @@ path_validation_alert({bad_cert, unknown_critical_extension}) -> ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); path_validation_alert({bad_cert, {revoked, _}}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); -%%path_validation_alert({bad_cert, revocation_status_undetermined}) -> -%% ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) -> Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE), Alert#alert{reason = Details}; |