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