File 4531-ssl-public_key-Extended-key-extension-handling.patch of Package erlang

From e7cd7fc40973493096f1582ae9089d87a7e88991 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Mon, 28 Aug 2023 15:48:34 +0200
Subject: [PATCH 1/2] ssl, public_key: Extended key extension handling

---
 lib/public_key/src/pubkey_cert.erl       | 27 ++++++++++++++++++++
 lib/public_key/test/public_key_SUITE.erl | 32 ++++++++++++++++++++++++
 lib/ssl/src/ssl_certificate.erl          | 30 ++++++++++++++--------
 3 files changed, 78 insertions(+), 11 deletions(-)

diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index 4d448fd42c..39084f3e76 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -905,6 +905,27 @@ validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyConstraints',
     validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon,
 			SelfSigned, UserState, VerifyFun);
 
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-extKeyUsage',
+                                           critical = true,
+                                           extnValue = KeyUse} = Extension | Rest],
+		    ValidationState#path_validation_state{last_cert = false}, ExistBasicCon,
+		    SelfSigned, UserState0, VerifyFun) ->
+    UserState =
+        case ext_keyusage_includes_any(KeyUse) of
+            true -> %% CA cert that specifies ?anyExtendedKeyUsage should not be marked critical
+                verify_fun(OtpCert, {bad_cert, invalid_ext_key_usage}, UserState0, VerifyFun);
+            false ->
+                verify_fun(OtpCert, {extension, Extension}, UserState0, VerifyFun);
+        end,
+    validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, SelfSigned,
+			UserState, VerifyFun);
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-extKeyUsage',
+                                           extnValue = KeyUse} = Extension | Rest],
+		    ValidationState, ExistBasicCon,
+		    SelfSigned, UserState0, VerifyFun) ->
+    UserState = verify_fun(OtpCert, {extension, Ext}, UserState0, VerifyFun),
+    validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, SelfSigned,
+                        UserState, VerifyFun);
 validate_extensions(OtpCert, [#'Extension'{} = Extension | Rest],
 		    ValidationState, ExistBasicCon,
 		    SelfSigned, UserState0, VerifyFun) ->
@@ -1483,3 +1504,9 @@ verify_options(#'RSASSA-PSS-params'{saltLength = SaltLen,
     [{rsa_padding, rsa_pkcs1_pss_padding},
      {rsa_pss_saltlen, SaltLen},
      {rsa_mgf1_md, HashAlgo}].
+
+
+ext_keyusage_includes_any(KeyUse) when is_list(KeyUse) ->
+    lists:member(?anyExtendedKeyUsage, KeyUse);
+ext_keyusage_includes_any(_) ->
+    false.
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 1a779e03bd..0edf3d29c6 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -95,6 +95,8 @@
          pkix_path_validation/1,
          pkix_path_validation_root_expired/0,
          pkix_path_validation_root_expired/1,
+         pkix_ext_key_usage/0,
+         pkix_ext_key_usage/1,
          pkix_verify_hostname_cn/1,
          pkix_verify_hostname_subjAltName/1,
          pkix_verify_hostname_options/1,
@@ -154,6 +156,7 @@ all() ->
      pkix_emailaddress, 
      pkix_path_validation,
      pkix_path_validation_root_expired,
+     pkix_ext_key_usage,
      pkix_iso_rsa_oid, 
      pkix_iso_dsa_oid, 
      pkix_rsa_md2_oid,
@@ -927,6 +930,35 @@ pkix_path_validation_root_expired(Config) when is_list(Config) ->
     Peer = proplists:get_value(cert, Conf),
     {error, {bad_cert, cert_expired}} = public_key:pkix_path_validation(Root, [ICA, Peer], []).
     
+pkix_ext_key_usage() ->
+    [{doc, "Extended key usage is usually in end entity certs, may be in CA but should not be critical in such case"}].
+pkix_ext_key_usage(Config) when is_list(Config) ->
+    SRootSpec = public_key:pkix_test_root_cert("OTP test server ROOT", []),
+    CRootSpec = public_key:pkix_test_root_cert("OTP test client ROOT", []),
+
+    FailCAExt = [#'Extension'{extnID = ?'id-ce-extKeyUsage',
+                              extnValue = [?'anyExtendedKeyUsage'],
+                              critical = true}],
+    CAExt = [#'Extension'{extnID = ?'id-ce-extKeyUsage',
+                          extnValue = [?'anyExtendedKeyUsage'],
+                          critical = false}],
+
+    #{server_config := SConf,
+      client_config := CConf} = public_key:pkix_test_data(#{server_chain => #{root => SRootSpec,
+                                                                             intermediates => [[{extensions, FailCAExt}]],
+                                                                             peer => []},
+                                                           client_chain => #{root => CRootSpec,
+                                                                             intermediates => [[{extensions, CAExt}]],
+                                                                             peer => []}}),
+    [_STRoot, SICA, SRoot] = proplists:get_value(cacerts, SConf),
+    [_CTRoot, CICA, CRoot] = proplists:get_value(cacerts, CConf),
+    SPeer = proplists:get_value(cert, SConf),
+    CPeer = proplists:get_value(cert, CConf),
+
+    {error, {bad_cert, invalid_ext_key_usage}} = public_key:pkix_path_validation(SRoot, [SICA, SPeer], []),
+
+    {ok, _} = public_key:pkix_path_validation(CRoot, [CICA, CPeer], []).
+
 %%--------------------------------------------------------------------
 %% To generate the PEM file contents:
 %%
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 2e2b43f564..cb1ddbfa05 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -203,12 +203,13 @@ file_to_crls(File, DbHandle) ->
 %% Description:  Validates ssl/tls specific extensions
 %%--------------------------------------------------------------------
 validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage',
-				    extnValue = KeyUse}}, UserState = #{role := Role}) ->
-    case is_valid_extkey_usage(KeyUse, Role) of
+                                    critical = Critical,
+				    extnValue = KeyUse}}, #{pathlen := 1} = UserState) ->
+    case is_valid_extkey_usage(KeyUse, Critical, UserState) of
 	true ->
 	    {valid, UserState};
 	false ->
-	    {fail, {bad_cert, invalid_ext_key_usage}}
+	    {unknown, UserState}
     end;
 validate(_, {extension, _}, UserState) ->
     {unknown, UserState};
@@ -216,14 +217,14 @@ validate(Issuer, {bad_cert, cert_expired}, #{issuer := Issuer}) ->
     {fail, {bad_cert, root_cert_expired}};
 validate(_, {bad_cert, _} = Reason, _) ->
     {fail, Reason};
-validate(Cert, valid, UserState) ->
+validate(Cert, valid, #{pathlen := N} = UserState) ->
     case verify_sign(Cert, UserState) of
         true ->
             case maps:get(cert_ext, UserState, undefined) of
                 undefined ->
-                    {valid, UserState};
+                    {valid, UserState#{pathlen => N-1}};
                 _ ->
-                    verify_cert_extensions(Cert, UserState)
+                    verify_cert_extensions(Cert, UserState#{pathlen => N -1})
             end;
         false ->
             {fail, {bad_cert, invalid_signature}}
@@ -493,13 +494,20 @@ do_find_issuer(IssuerFun, CertDbHandle, CertDb) ->
 	throw:{ok, _} = Return ->
 	    Return
     end.
-	
-is_valid_extkey_usage(KeyUse, client) ->
+
+is_valid_extkey_usage(KeyUse, true, #{role := Role, pathlen := 1}) when is_list(KeyUse) ->
+    is_valid_key_usage(ext_keysage(Role), KeyUse);
+is_valid_key_usage(KeyUse, true, #{role := Role, pathlen := 1}) ->
+    is_valid_key_usage(ext_keysage(Role), [KeyUse]);
+is_valid_key_usage(_, false, _) ->
+    false.
+
+ext_keysage(client) ->
     %% Client wants to verify server
-    is_valid_key_usage(KeyUse,?'id-kp-serverAuth');
-is_valid_extkey_usage(KeyUse, server) ->
+    ?'id-kp-serverAuth'.
+ext_keysage(server) ->
     %% Server wants to verify client
-    is_valid_key_usage(KeyUse, ?'id-kp-clientAuth').
+    ?'id-kp-serverAuth'.
 
 verify_cert_signer(BinCert, SignerTBSCert) ->
     PublicKey = public_key(SignerTBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo),
-- 
2.35.3

openSUSE Build Service is sponsored by