File 5032-ssl-Prepare-to-use-extensions-from-TLS-1.3-certifica.patch of Package erlang
From 85ec699aca8bf83c009b06cb1dc7348f5171825d Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Tue, 19 May 2020 16:51:32 +0200
Subject: [PATCH 2/2] ssl: Prepare to use extensions from TLS-1.3
#certificate_entry{}
---
lib/ssl/src/ssl_certificate.erl | 70 +++++++++++++++++--
lib/ssl/src/ssl_connection.erl | 7 +-
lib/ssl/src/ssl_handshake.erl | 58 ++++++++--------
lib/ssl/src/tls_handshake_1_3.erl | 107 +++++++++++++-----------------
4 files changed, 147 insertions(+), 95 deletions(-)
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index ade1d396cd..34905e69d9 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -141,7 +141,7 @@ file_to_crls(File, DbHandle) ->
%% Description: Validates ssl/tls specific extensions
%%--------------------------------------------------------------------
validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage',
- extnValue = KeyUse}}, UserState = {Role, _,_, _, _, _}) ->
+ extnValue = KeyUse}}, UserState = #{role := Role}) ->
case is_valid_extkey_usage(KeyUse, Role) of
true ->
{valid, UserState};
@@ -152,12 +152,28 @@ validate(_, {extension, _}, UserState) ->
{unknown, UserState};
validate(_, {bad_cert, _} = Reason, _) ->
{fail, Reason};
-validate(_, valid, UserState) ->
- {valid, UserState};
-validate(Cert, valid_peer, UserState = {client, _,_, {Hostname, Customize}, _, _}) when Hostname =/= disable ->
- verify_hostname(Hostname, Customize, Cert, UserState);
-validate(_, valid_peer, UserState) ->
- {valid, UserState}.
+validate(Cert, valid, UserState) ->
+ case verify_sign(Cert, UserState) of
+ true ->
+ case maps:get(cert_ext, UserState, undefined) of
+ undefined ->
+ {valid, UserState};
+ _ ->
+ verify_cert_extensions(Cert, UserState)
+ end;
+ false ->
+ {fail, {bad_cert, invalid_signature}}
+ end;
+validate(Cert, valid_peer, UserState = #{role := client, server_name := Hostname,
+ customize_hostname_check := Customize}) when Hostname =/= disable ->
+ case verify_hostname(Hostname, Customize, Cert, UserState) of
+ {valid, UserState} ->
+ validate(Cert, valid, UserState);
+ Error ->
+ Error
+ end;
+validate(Cert, valid_peer, UserState) ->
+ validate(Cert, valid, UserState).
%%--------------------------------------------------------------------
-spec is_valid_key_usage(list(), term()) -> boolean().
@@ -396,3 +412,43 @@ verify_hostname(Hostname, Customize, Cert, UserState) ->
false ->
{fail, {bad_cert, hostname_check_failed}}
end.
+
+verify_cert_extensions(Cert, #{cert_ext := CertExts} = UserState) ->
+ Id = public_key:pkix_subject_id(Cert),
+ Extensions = maps:get(Id, CertExts, []),
+ verify_cert_extensions(Cert, UserState, Extensions, #{}).
+
+verify_cert_extensions(_, UserState, [], _) ->
+ {valid, UserState};
+verify_cert_extensions(Cert, UserState, [ _ | Exts], Context) ->
+ %% TODO Skip unknow extensions ?
+ verify_cert_extensions(Cert, UserState, Exts, Context).
+
+verify_sign(_, #{version := {_, Minor}}) when Minor < 3 ->
+ %% This verification is not applicable pre TLS-1.2
+ true;
+verify_sign(Cert, #{signature_algs := SignAlgs,
+ signature_algs_cert := undefined}) ->
+ is_supported_signature_algorithm(Cert, SignAlgs);
+verify_sign(Cert, #{signature_algs_cert := SignAlgs}) ->
+ is_supported_signature_algorithm(Cert, SignAlgs).
+
+is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm =
+ #'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1'}}, [{_,_}|_] = SignAlgs) ->
+ lists:member({sha, dsa}, SignAlgs);
+is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = SignAlg}, [{_,_}|_] = SignAlgs) ->
+ Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
+ {Hash, Sign, _ } = ssl_cipher:scheme_to_components(Scheme),
+ lists:member({pre_1_3_hash(Hash), pre_1_3_sign(Sign)}, SignAlgs);
+is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) ->
+ Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
+ lists:member(Scheme, SignAlgs).
+
+pre_1_3_sign(rsa_pkcs1) ->
+ rsa;
+pre_1_3_sign(Other) ->
+ Other.
+pre_1_3_hash(sha1) ->
+ sha;
+pre_1_3_hash(Hash) ->
+ Hash.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 8ee9261c38..d73558da23 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1003,7 +1003,7 @@ certify(internal, #certificate{} = Cert,
connection_env = #connection_env{negotiated_version = Version},
ssl_options = Opts} = State, Connection) ->
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
- Opts, CRLDbInfo, Role, Host) of
+ Opts, CRLDbInfo, Role, Host, ensure_tls(Version)) of
{PeerCert, PublicKeyInfo} ->
handle_peer_cert(Role, PeerCert, PublicKeyInfo,
State#state{client_certificate_requested = false}, Connection);
@@ -3128,3 +3128,8 @@ is_sni_value(Hostname) ->
_ ->
true
end.
+
+ensure_tls({254, _} = Version) ->
+ dtls_v1:corresponding_tls_version(Version);
+ensure_tls(Version) ->
+ Version.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index c17062d888..374539afe6 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -51,7 +51,7 @@
finished/5, next_protocol/1]).
%% Handle handshake messages
--export([certify/7, certificate_verify/6, verify_signature/5,
+-export([certify/8, certificate_verify/6, verify_signature/5,
master_secret/4, server_key_exchange_hash/2, verify_connection/6,
init_handshake_history/0, update_handshake_history/2, verify_server_key/5,
select_version/3, select_supported_version/2, extension_value/1
@@ -82,7 +82,7 @@
-export([get_cert_params/1,
server_name/3,
- validation_fun_and_state/10,
+ validation_fun_and_state/4,
handle_path_validation_error/7]).
%%====================================================================
@@ -337,7 +337,8 @@ next_protocol(SelectedProtocol) ->
%%====================================================================
%%--------------------------------------------------------------------
-spec certify(#certificate{}, db_handle(), certdb_ref(), ssl_options(), term(),
- client | server, inet:hostname() | inet:ip_address()) -> {der_cert(), public_key_info()} | #alert{}.
+ client | server, inet:hostname() | inet:ip_address(),
+ ssl_record:ssl_version()) -> {der_cert(), public_key_info()} | #alert{}.
%%
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
@@ -348,7 +349,8 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
customize_hostname_check := CustomizeHostnameCheck,
crl_check := CrlCheck,
log_level := Level,
- depth := Depth} = Opts, CRLDbHandle, Role, Host) ->
+ signature_algs := SignAlgos,
+ depth := Depth} = Opts, CRLDbHandle, Role, Host, Version) ->
ServerName = server_name(ServerNameIndication, Host, Role),
[PeerCert | ChainCerts ] = ASN1Certs,
@@ -356,10 +358,18 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
{TrustedCert, CertPath} =
ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef,
PartialChain),
- ValidationFunAndState = validation_fun_and_state(VerifyFun, Role,
- CertDbHandle, CertDbRef, ServerName,
- CustomizeHostnameCheck,
- CrlCheck, CRLDbHandle, CertPath, Level),
+ ValidationFunAndState = validation_fun_and_state(VerifyFun, #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ signature_algs => SignAlgos,
+ signature_algs_cert => undefined,
+ version => Version,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle},
+ CertPath, Level),
Options = [{max_path_length, Depth},
{verify_fun, ValidationFunAndState}],
case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
@@ -1636,9 +1646,7 @@ certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) ->
[], CertDbData).
%%-------------Handle handshake messages --------------------------------
-validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
- ServerNameIndication, CustomizeHostCheck, CRLCheck,
- CRLDbHandle, CertPath, LogLevel) ->
+validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) ->
{fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) ->
case ssl_certificate:validate(OtpCert,
Extension,
@@ -1655,18 +1663,15 @@ validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
(OtpCert, VerifyResult, {SslState, UserState}) ->
apply_user_fun(Fun, OtpCert, VerifyResult, UserState,
SslState, CertPath, LogLevel)
- end, {{Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}, UserState0}};
-validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
- ServerNameIndication, CustomizeHostCheck, CRLCheck,
- CRLDbHandle, CertPath, LogLevel) ->
+ end, {VerifyState, UserState0}};
+validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) ->
{fun(OtpCert, {extension, _} = Extension, SslState) ->
ssl_certificate:validate(OtpCert,
Extension,
SslState);
(OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or
(VerifyResult == valid_peer) ->
- case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
- CRLDbHandle, VerifyResult, CertPath, LogLevel) of
+ case crl_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of
valid ->
ssl_certificate:validate(OtpCert,
VerifyResult,
@@ -1678,15 +1683,13 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
ssl_certificate:validate(OtpCert,
VerifyResult,
SslState)
- end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}.
+ end, VerifyState}.
-apply_user_fun(Fun, OtpCert, VerifyResult, UserState0,
- {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath, LogLevel) when
+apply_user_fun(Fun, OtpCert, VerifyResult, UserState0, SslState, CertPath, LogLevel) when
(VerifyResult == valid) or (VerifyResult == valid_peer) ->
case Fun(OtpCert, VerifyResult, UserState0) of
{Valid, UserState} when (Valid == valid) or (Valid == valid_peer) ->
- case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
- CRLDbHandle, VerifyResult, CertPath, LogLevel) of
+ case crl_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of
valid ->
{Valid, {SslState, UserState}};
Result ->
@@ -1722,7 +1725,7 @@ handle_incomplete_chain(PeerCert, Chain0,
CertDbHandle, CertsDbRef,
PartialChain) of
{unknown_ca, []} ->
- path_validation_alert(Reason);
+ path_validation_alert(Reason);
{Trusted, Path} ->
case public_key:pkix_path_validation(Trusted, Path, Options) of
{ok, {PublicKeyInfo,_}} ->
@@ -1812,11 +1815,14 @@ bad_key(#{algorithm := rsa}) ->
bad_key(#'ECPrivateKey'{}) ->
unacceptable_ecdsa_key.
-crl_check(_, false, _,_,_, _, _, _) ->
+crl_check(_, #{crl_check := false}, _, _, _) ->
valid;
-crl_check(_, peer, _, _,_, valid, _, _) -> %% Do not check CAs with this option.
+crl_check(_, #{crl_check := peer}, _, valid, _) -> %% Do not check CAs with this option.
valid;
-crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath, LogLevel) ->
+crl_check(OtpCert, #{crl_check := Check,
+ certdb := CertDbHandle,
+ certdb_ref := CertDbRef,
+ crl_db := {Callback, CRLDbHandle}}, _, CertPath, LogLevel) ->
Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) ->
ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath,
DBInfo})
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 3dc01fbcb0..f60fd0de30 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -1283,10 +1283,8 @@ process_certificate(#certificate_1_3{
State1 = calculate_traffic_secrets(State0),
State = ssl_record:step_encryption_state(State1),
{error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}};
-process_certificate(#certificate_1_3{certificate_list = Certs0},
- #state{ssl_options =
- #{signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert} = SslOptions,
+process_certificate(#certificate_1_3{certificate_list = CertEntries},
+ #state{ssl_options = SslOptions,
static_env =
#static_env{
role = Role,
@@ -1294,45 +1292,20 @@ process_certificate(#certificate_1_3{certificate_list = Certs0},
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
crl_db = CRLDbHandle}} = State0) ->
- %% TODO: handle extensions!
- %% Remove extensions from list of certificates!
- Certs = convert_certificate_chain(Certs0),
- case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of
- true ->
- case validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
- SslOptions, CRLDbHandle, Role, Host) of
- {ok, {PeerCert, PublicKeyInfo}} ->
- State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
- {ok, {State, wait_cv}};
- {error, Reason} ->
- State = update_encryption_state(Role, State0),
- {error, {Reason, State}};
- {ok, #alert{} = Alert} ->
- State = update_encryption_state(Role, State0),
- {error, {Alert, State}}
- end;
- false ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- "Client certificate uses unsupported signature algorithm"), State}}
+
+ case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
+ SslOptions, CRLDbHandle, Role, Host) of
+ {ok, {PeerCert, PublicKeyInfo}} ->
+ State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
+ {ok, {State, wait_cv}};
+ {error, Reason} ->
+ State = update_encryption_state(Role, State0),
+ {error, {Reason, State}};
+ {ok, #alert{} = Alert} ->
+ State = update_encryption_state(Role, State0),
+ {error, {Alert, State}}
end.
-
-%% TODO: check whole chain!
-is_supported_signature_algorithm(Certs, SignAlgs, undefined) ->
- is_supported_signature_algorithm(Certs, SignAlgs);
-is_supported_signature_algorithm(Certs, _, SignAlgsCert) ->
- is_supported_signature_algorithm(Certs, SignAlgsCert).
-%%
-is_supported_signature_algorithm([BinCert|_], SignAlgs0) ->
- #'OTPCertificate'{signatureAlgorithm = SignAlg} =
- public_key:pkix_decode_cert(BinCert, otp),
- SignAlgs = filter_tls13_algs(SignAlgs0),
- Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
- lists:member(Scheme, SignAlgs).
-
-
%% Sets correct encryption state when sending Alerts in shared states that use different secrets.
%% - If client: use handshake secrets.
%% - If server: use traffic secrets as by this time the client's state machine
@@ -1344,30 +1317,41 @@ update_encryption_state(client, State) ->
State.
-validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
+validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
#{server_name_indication := ServerNameIndication,
partial_chain := PartialChain,
verify_fun := VerifyFun,
customize_hostname_check := CustomizeHostnameCheck,
crl_check := CrlCheck,
log_level := LogLevel,
- depth := Depth} = SslOptions,
- CRLDbHandle, Role, Host) ->
+ depth := Depth,
+ signature_algs := SignAlgs,
+ signature_algs_cert := SignAlgsCert
+ } = SslOptions, CRLDbHandle, Role, Host) ->
+ {Certs, CertExt} = split_cert_entries(CertEntries),
ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role),
[PeerCert | ChainCerts ] = Certs,
- try
- {TrustedCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef,
+ try
+ {TrustedCert, CertPath} =
+ ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef,
PartialChain),
ValidationFunAndState =
- ssl_handshake:validation_fun_and_state(VerifyFun, Role,
- CertDbHandle, CertDbRef, ServerName,
- CustomizeHostnameCheck,
- CrlCheck, CRLDbHandle, CertPath, LogLevel),
+ ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ signature_algs => filter_tls13_algs(SignAlgs),
+ signature_algs_cert => filter_tls13_algs(SignAlgsCert),
+ version => {3,4},
+ cert_ext => CertExt
+ },
+ CertPath, LogLevel),
Options = [{max_path_length, Depth},
{verify_fun, ValidationFunAndState}],
- %% TODO: Validate if Certificate is using a supported signature algorithm
- %% (signature_algs_cert)!
case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
{ok, {PublicKeyInfo,_}} ->
{ok, {PeerCert, PublicKeyInfo}};
@@ -1384,20 +1368,21 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
{error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})}
end.
-
store_peer_cert(#state{session = Session,
handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
State#state{session = Session#session{peer_certificate = PeerCert},
handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}.
-convert_certificate_chain(Certs) ->
- Fun = fun(#certificate_entry{data = Data}) ->
- {true, Data};
- (_) ->
- false
- end,
- lists:filtermap(Fun, Certs).
+split_cert_entries(CertEntries) ->
+ split_cert_entries(CertEntries, [], #{}).
+split_cert_entries([], Chain, Ext) ->
+ {lists:reverse(Chain), Ext};
+split_cert_entries([#certificate_entry{data = DerCert,
+ extensions = Extensions0} | CertEntries], Chain, Ext) ->
+ Id = public_key:pkix_subject_id(DerCert),
+ Extensions = maps:to_list(Extensions0),
+ split_cert_entries(CertEntries, [DerCert | Chain], Ext#{Id => Extensions}).
%% 4.4.1. The Transcript Hash
--
2.26.2