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

openSUSE Build Service is sponsored by