From 17a8022ae6b7eb9147f32a2db2544feeb813e0d6 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 20 Sep 2024 12:02:54 +0200 Subject: [PATCH 1/3] public_key: CA extended key usage check Should check CAs extended key usage conforms with key usage and therby handle possible critical extension. Closes #8806 --- lib/public_key/src/pubkey_cert.erl | 81 +++++++++++++++++++++--- lib/public_key/src/public_key.erl | 1 + lib/public_key/test/public_key_SUITE.erl | 69 +++++++++++++++++++- 3 files changed, 142 insertions(+), 9 deletions(-) diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index d26b2b304cac..75b658f3fad1 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -701,9 +701,20 @@ validate_extensions(Cert, asn1_NOVALUE, ValidationState, ExistBasicCon, validate_extensions(Cert, [], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(_,[], ValidationState, basic_constraint, _SelfSigned, - UserState, _) -> - {ValidationState, UserState}; +validate_extensions(#cert{otp = OtpCert} = Cert,[], ValidationState, basic_constraint, _SelfSigned, + UserState0, VerifyFun) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions), + KeyUseExt = pubkey_cert:select_extension(?'id-ce-keyUsage', Extensions), + ExtKeyUseExt = pubkey_cert:select_extension(?'id-ce-extKeyUsage', Extensions), + case compatible_ext_key_usage(KeyUseExt, ExtKeyUseExt) of + true -> + {ValidationState, UserState0}; + false -> + UserState = verify_fun(Cert, {bad_cert, {key_usage_mismatch, {KeyUseExt, ExtKeyUseExt}}}, + UserState0, VerifyFun), + {ValidationState, UserState} + end; validate_extensions(Cert, [], ValidationState = #path_validation_state{max_path_length = Len, last_cert = Last}, @@ -832,15 +843,20 @@ validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-inhibitAnyPolicy'} = Ex SelfSigned, UserState, VerifyFun); validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-extKeyUsage', critical = true, - extnValue = KeyUse} = Extension | Rest], + extnValue = ExtKeyUse} = Extension | Rest], #path_validation_state{last_cert = false} = ValidationState, ExistBasicCon, SelfSigned, UserState0, VerifyFun) -> UserState = - case ext_keyusage_includes_any(KeyUse) of + case ext_keyusage_includes_any(ExtKeyUse) of true -> %% CA cert that specifies ?anyExtendedKeyUsage should not be marked critical verify_fun(Cert, {bad_cert, invalid_ext_key_usage}, UserState0, VerifyFun); false -> - verify_fun(Cert, {extension, Extension}, UserState0, VerifyFun) + case ca_known_extend_key_use(ExtKeyUse) of + true -> + UserState0; + false -> + verify_fun(Cert, {extension, Extension}, UserState0, VerifyFun) + end end, validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); @@ -1783,8 +1799,57 @@ is_digitally_sign_cert(Cert) -> lists:member(keyCertSign, KeyUse) end. -missing_basic_constraints(Cert, SelfSigned, ValidationState, VerifyFun, UserState0,Len) -> - UserState = verify_fun(Cert, {bad_cert, missing_basic_constraint}, +compatible_ext_key_usage(_, undefined) -> + true; +compatible_ext_key_usage(#'Extension'{extnValue = KeyUse}, #'Extension'{extnValue = Purposes}) -> + case ext_keyusage_includes_any(Purposes) of + true -> + true; + false -> + is_compatible_purposes(KeyUse, Purposes) + end. + +is_compatible_purposes(_, []) -> + true; +is_compatible_purposes(KeyUse, [?'id-kp-serverAuth'| Rest]) -> + (lists:member(digitalSignature, KeyUse) orelse + lists:member(keyAgreement, KeyUse)) andalso + is_compatible_purposes(KeyUse, Rest); +is_compatible_purposes(KeyUse, [?'id-kp-clientAuth'| Rest]) -> + (lists:member(digitalSignature, KeyUse) + orelse + (lists:member(keyAgreement, KeyUse) orelse lists:member(keyEncipherment, KeyUse))) + andalso is_compatible_purposes(KeyUse, Rest); +is_compatible_purposes(KeyUse, [?'id-kp-codeSigning'| Rest]) -> + lists:member(digitalSignature, KeyUse) andalso + is_compatible_purposes(KeyUse, Rest); +is_compatible_purposes(KeyUse, [?'id-kp-emailProtection'| Rest]) -> + ((lists:member(digitalSignature, KeyUse) orelse + lists:member(nonRepudiation, KeyUse)) + orelse + (lists:member(keyAgreement, KeyUse) orelse lists:member(keyEncipherment, KeyUse))) + andalso is_compatible_purposes(KeyUse, Rest); +is_compatible_purposes(KeyUse, [Id| Rest]) when Id == ?'id-kp-timeStamping'; + Id == ?'id-kp-OCSPSigning'-> + (lists:member(digitalSignature, KeyUse) orelse + lists:member(nonRepudiation, KeyUse)) andalso + is_compatible_purposes(KeyUse, Rest); +is_compatible_purposes(KeyUse, [_| Rest]) -> %% Unknown purposes are for user verify_fun to care about + is_compatible_purposes(KeyUse, Rest). + +ca_known_extend_key_use(ExtKeyUse) -> + CAExtSet = ca_known_ext_key_usage(), + Intersertion = sets:intersection(CAExtSet, sets:from_list(ExtKeyUse)), + not sets:is_empty(Intersertion). + +ca_known_ext_key_usage() -> + %% Following extended key usages are known + sets:from_list([?'id-kp-serverAuth', ?'id-kp-clientAuth', + ?'id-kp-codeSigning', ?'id-kp-emailProtection', + ?'id-kp-timeStamping', ?'id-kp-OCSPSigning']). + +missing_basic_constraints(OtpCert, SelfSigned, ValidationState, VerifyFun, UserState0,Len) -> + UserState = verify_fun(OtpCert, {bad_cert, missing_basic_constraint}, UserState0, VerifyFun), case SelfSigned of true -> diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 88761a74e60f..d31169da08d7 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -308,6 +308,7 @@ The reason that a certifcate gets rejected by the certificate path validation. """. -type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature | name_not_permitted | missing_basic_constraint | invalid_key_usage | duplicate_cert_in_path | + {key_usage_mismatch, term()} | {'policy_requirement_not_met', term()} | {'invalid_policy_mapping', term()} | {revoked, crl_reason()} | invalid_validity_dates | {revocation_status_undetermined, term()} | atom(). diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index a57e5b26e226..18dac2a687d6 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -103,6 +103,8 @@ pkix_path_validation_root_expired/1, pkix_ext_key_usage/0, pkix_ext_key_usage/1, + pkix_ext_key_usage_any/0, + pkix_ext_key_usage_any/1, pkix_path_validation_bad_date/0, pkix_path_validation_bad_date/1, pkix_verify_hostname_cn/1, @@ -170,6 +172,7 @@ all() -> pkix_path_validation, pkix_path_validation_root_expired, pkix_ext_key_usage, + pkix_ext_key_usage_any, pkix_path_validation_bad_date, pkix_iso_rsa_oid, pkix_iso_dsa_oid, @@ -1006,11 +1009,75 @@ pkix_path_validation_root_expired(Config) when is_list(Config) -> {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"}]. + [{doc, "If extended key usage is a critical extension in a CA (usually not included) make sure it is compatible with keyUsage extension"}]. 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", []), + CAExtServer = [#'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = [digitalSignature, keyCertSign, cRLSign], + critical = false}, + #'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = [?'id-kp-OCSPSigning', ?'id-kp-emailProtection', ?'id-kp-serverAuth'], + critical = true}], + + CAExtClient = [#'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = [digitalSignature, keyCertSign, cRLSign], + critical = false}, + #'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = [?'id-kp-codeSigning', ?'id-kp-emailProtection', ?'id-kp-clientAuth'], + critical = true}], + #{server_config := SConf, + client_config := CConf} = public_key:pkix_test_data(#{server_chain => #{root => SRootSpec, + intermediates => [[{extensions, CAExtServer}]], + peer => []}, + client_chain => #{root => CRootSpec, + intermediates => [[{extensions, CAExtClient}]], + 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), + + {ok, _} = public_key:pkix_path_validation(SRoot, [SICA, SPeer], []), + {ok, _} = public_key:pkix_path_validation(CRoot, [CICA, CPeer], []), + + CAExtServerFail = [#'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = [keyAgreement, keyCertSign, cRLSign], + critical = false}, + #'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = [?'id-kp-serverAuth', ?'id-kp-timeStamping'], + critical = true}], + + CAExtClient1 = [#'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = [keyEncipherment, keyCertSign, cRLSign], + critical = false}, + #'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = [?'id-kp-emailProtection', ?'id-kp-clientAuth'], + critical = true}], + + #{server_config := SConf1, + client_config := CConf1} = public_key:pkix_test_data(#{server_chain => #{root => SRootSpec, + intermediates => [[{extensions, CAExtServerFail}]], + peer => []}, + client_chain => #{root => CRootSpec, + intermediates => [[{extensions, CAExtClient1}]], + peer => []}}), + [_, SICA1, SRoot1] = proplists:get_value(cacerts, SConf1), + SPeer1 = proplists:get_value(cert, SConf1), + + {error, {bad_cert,{key_usage_mismatch, _}}} = public_key:pkix_path_validation(SRoot1, [SICA1, SPeer1], []), + + [_, CICA1, CRoot1] = proplists:get_value(cacerts, CConf1), + CPeer1 = proplists:get_value(cert, CConf1), + {ok, _} = public_key:pkix_path_validation(CRoot1, [CICA1, CPeer1], []). + +pkix_ext_key_usage_any() -> + [{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_any(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}], From 630a67960570b9776a81ca262857092b47aef105 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 27 Nov 2024 14:49:22 +0100 Subject: [PATCH 2/3] public_key: Also apply to end entity cert OTP-19240 part of final solution for PR-8840 --- lib/public_key/src/pubkey_cert.erl | 47 +++++++++++++++++------------- lib/public_key/src/public_key.erl | 4 +-- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index 75b658f3fad1..320d04cd8cad 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -701,20 +701,10 @@ validate_extensions(Cert, asn1_NOVALUE, ValidationState, ExistBasicCon, validate_extensions(Cert, [], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(#cert{otp = OtpCert} = Cert,[], ValidationState, basic_constraint, _SelfSigned, +validate_extensions(Cert,[], ValidationState, basic_constraint, _SelfSigned, UserState0, VerifyFun) -> - TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, - Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions), - KeyUseExt = pubkey_cert:select_extension(?'id-ce-keyUsage', Extensions), - ExtKeyUseExt = pubkey_cert:select_extension(?'id-ce-extKeyUsage', Extensions), - case compatible_ext_key_usage(KeyUseExt, ExtKeyUseExt) of - true -> - {ValidationState, UserState0}; - false -> - UserState = verify_fun(Cert, {bad_cert, {key_usage_mismatch, {KeyUseExt, ExtKeyUseExt}}}, - UserState0, VerifyFun), - {ValidationState, UserState} - end; + UserState = validate_ext_key_usage(Cert, UserState0, VerifyFun, ca), + {ValidationState, UserState}; validate_extensions(Cert, [], ValidationState = #path_validation_state{max_path_length = Len, last_cert = Last}, @@ -723,8 +713,9 @@ validate_extensions(Cert, [], ValidationState = true when SelfSigned -> {ValidationState, UserState0}; true -> + UserState = validate_ext_key_usage(Cert, UserState0, VerifyFun, endentity), {ValidationState#path_validation_state{max_path_length = Len - 1}, - UserState0}; + UserState}; false -> %% basic_constraint must appear in certs used for digital sign %% see 4.2.1.10 in rfc 3280 @@ -737,7 +728,6 @@ validate_extensions(Cert, [], ValidationState = {ValidationState, UserState0} end end; - validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-basicConstraints', extnValue = @@ -890,6 +880,18 @@ handle_last_cert(Cert, #path_validation_state{last_cert = true, handle_last_cert(_, ValidationState) -> ValidationState. +validate_ext_key_usage(#cert{otp = OtpCert} = Cert, UserState, VerifyFun, Type) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions), + KeyUseExt = pubkey_cert:select_extension(?'id-ce-keyUsage', Extensions), + ExtKeyUseExt = pubkey_cert:select_extension(?'id-ce-extKeyUsage', Extensions), + case compatible_ext_key_usage(KeyUseExt, ExtKeyUseExt, Type) of + true -> + UserState; + false -> + verify_fun(Cert, {bad_cert, {key_usage_mismatch, {KeyUseExt, ExtKeyUseExt}}}, + UserState, VerifyFun) + end. %%==================================================================== %% Policy handling @@ -1799,9 +1801,11 @@ is_digitally_sign_cert(Cert) -> lists:member(keyCertSign, KeyUse) end. -compatible_ext_key_usage(_, undefined) -> +compatible_ext_key_usage(undefined, _, endentity) -> %% keyusage (first arg )is mandantory in CAs true; -compatible_ext_key_usage(#'Extension'{extnValue = KeyUse}, #'Extension'{extnValue = Purposes}) -> +compatible_ext_key_usage(_, undefined, _) -> + true; +compatible_ext_key_usage(#'Extension'{extnValue = KeyUse}, #'Extension'{extnValue = Purposes}, _) -> case ext_keyusage_includes_any(Purposes) of true -> true; @@ -2104,7 +2108,7 @@ extensions(Role, Type, Opts) -> add_default_extensions(_, ca, Exts) -> Default = [#'Extension'{extnID = ?'id-ce-keyUsage', - extnValue = [keyCertSign, cRLSign], + extnValue = [keyCertSign, digitalSignature, cRLSign], critical = false}, #'Extension'{extnID = ?'id-ce-basicConstraints', extnValue = #'BasicConstraints'{cA = true}, @@ -2121,9 +2125,12 @@ add_default_extensions(server, peer, Exts) -> critical = false} ], add_default_extensions(Default, Exts); - add_default_extensions(client, peer, Exts) -> - Exts. + Default = [#'Extension'{extnID = ?'id-ce-keyUsage', + extnValue = [digitalSignature], + critical = false} + ], + add_default_extensions(Default, Exts). add_default_extensions(Defaults0, Exts) -> Defaults = lists:filtermap(fun(#'Extension'{extnID = ID} = Ext) -> diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index d31169da08d7..a190989c286f 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -1567,7 +1567,7 @@ Available options: ```erlang fun(OtpCert :: #'OTPCertificate'{}, - Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | + Event :: {bad_cert, Reason :: bad_cert_reason() | {revoked, atom()}} | {extension, #'Extension'{}}, UserState :: term()) -> {valid, UserState :: term()} | @@ -1581,7 +1581,7 @@ Available options: ```erlang fun(OtpCert :: #'OTPCertificate'{}, DerCert :: der_encoded(), - Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | + Event :: {bad_cert, Reason :: bad_cert_reason() | {revoked, atom()}} | {extension, #'Extension'{}}, UserState :: term()) -> {valid, UserState :: term()} | From e1fdb78bcfc38b7cf0a347cb83a979992d69dcac Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 11 Nov 2024 11:09:08 +0100 Subject: [PATCH 3/3] ssl: Peer ext-keyusage extension should alwyas be verified if present Critical extension is not relevant in this context, when we know the extension it should always be verified. If present in a itermidate CA the extension may also be acceptable if the usage is relaxed by the special "any" value, option allow_any_ca_purpose is added to configure that. --- lib/ssl/src/ssl.app.src | 2 +- lib/ssl/src/ssl.erl | 38 ++++- lib/ssl/src/ssl_certificate.erl | 92 +++++++----- lib/ssl/src/ssl_handshake.erl | 37 ++++- lib/ssl/src/tls_handshake_1_3.erl | 2 + lib/ssl/test/ssl_api_SUITE.erl | 9 ++ lib/ssl/test/ssl_cert_SUITE.erl | 241 ++++++++++++++++++++++++------ 7 files changed, 332 insertions(+), 89 deletions(-) diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index f3bc8ecb5aae..fa67c6ed0e4c 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -92,6 +92,6 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-6.0","public_key-1.16.2","kernel-9.0", + {runtime_dependencies, ["stdlib-6.0","public_key-1.16.4","kernel-9.0", "erts-15.0","crypto-5.0", "inets-5.10.7", "runtime_tools-1.15.1"]}]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 6d00dd39364d..c73d6e053ce9 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -841,6 +841,20 @@ Common certificate related options to both client and server. The chain consisted only of one self-signed certificate. + - **{invalid_ext_keyusage, [public_key:oid()]} ** + + If the peer certificate specifies the extended keyusage extension and does + not include the purpose for either being a TLS server (id-kp-ServerAuth) or + TLS client (id-kp-ClientAuth) depending on the peers role. + + - **{ca_invalid_ext_keyusage, [public_key:oid()]} ** + + If a CA certificate specifies the extended keyusage extension and does + not include the purpose for either being a TLS server + (id-kp-ServerAuth) or TLS client (id-kp-ClientAuth) depending + on the role of the peer chained with this CA, or the option allow_any_ca_purpose is set to `true` + but the special any-value (anyExtendedKeyUsage) is not included in the CA cert purposes. + - **`PKIX X-509-path validation error`** For possible reasons, see `public_key:pkix_path_validation/3`. @@ -851,6 +865,13 @@ Common certificate related options to both client and server. see [public_key:pkix_path_validation/3](`public_key:pkix_path_validation/3`) for more details. +- **`{allow_any_ca_purpose, boolean()}`** - Handle certificate extended key usages extension + + If a CA certificate has an extended key usage extension but it does not want to + restrict the usages of the key it can include a special `anyExtendedKeyUsage` purpose. + If this is option is set to `true` all key usage purposes is automatically + accepted for a CA that include that purpose, the options default to false. + - **`{cerl_check, Check}`** - Handle certificate revocation lists. Perform CRL (Certificate Revocation List) verification @@ -886,6 +907,7 @@ Common certificate related options to both client and server. {explicit_policy, boolean()} | {inhibit_policy_mapping, boolean()} | {inhibit_any_policy, boolean()}]} | + {allow_any_ca_purpose, Allow::boolean()} | {crl_check, Check::boolean() | peer | best_effort} | {crl_cache, crl_cache_opts()} | {partial_chain, anchor_fun()}. @@ -3695,6 +3717,7 @@ ssl_options() -> use_srtp, user_lookup_fun, verify, verify_fun, cert_policy_opts, + allow_any_ca_purpose, versions ]. @@ -3863,7 +3886,7 @@ opt_verification(UserOpts, Opts0, #{role := Role} = Env) -> Opts3 = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2), - Opts = case Role of + Opts4 = case Role of client -> opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain}, Env); @@ -3872,7 +3895,8 @@ opt_verification(UserOpts, Opts0, #{role := Role} = Env) -> fail_if_no_peer_cert => FailNoPeerCert}, Env) end, - opt_policies(UserOpts, Opts). + Opts = opt_policies(UserOpts, Opts4), + opt_extend_keyusage(UserOpts, Opts). default_verify(client) -> %% Server authenication is by default requiered @@ -3937,6 +3961,16 @@ opt_policies(UserOpts, Opts) -> Opts#{cert_policy_opts => POpts} end. +opt_extend_keyusage(UserOpts, Opts) -> + case get_opt_bool(allow_any_ca_purpose, false, UserOpts, Opts) of + {default, Value} -> + Opts#{allow_any_ca_purpose => Value}; + {old, _OldValue} -> + Opts; + {new, NewValue} -> + Opts#{allow_any_ca_purpose => NewValue} + end. + validate_policy_opts([]) -> true; validate_policy_opts([{policy_set, OidList} | Rest]) when is_list(OidList) -> diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 6a1dfa5a633e..5723ab8730a7 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -202,25 +202,42 @@ file_to_crls(File, DbHandle) -> %% %% Description: Validates ssl/tls specific extensions %%-------------------------------------------------------------------- -validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', - critical = Critical, - extnValue = KeyUse}}, #{path_len := 1} = UserState, - _LogLevel) -> - %% If extension in peer, check for TLS server/client usage - case is_valid_extkey_usage(KeyUse, Critical, UserState) of - true -> - {valid, UserState}; - false -> - {unknown, UserState} +validate(_, {extension, #'Extension'{extnID = ?'id-ce-extKeyUsage'} = Ext}, #{path_len := 1} = UserState, _) -> + case verify_extkeyusage(Ext, UserState) of + valid -> + {valid, UserState}; + KeyUses -> + {fail, {bad_cert, {invalid_ext_keyusage, KeyUses}}} end; -validate(_, {extension, _}, UserState, _LogLevel) -> +validate(_, {extension, #'Extension'{extnID = ?'id-ce-extKeyUsage'} = Ext}, UserState, _) -> + case verify_extkeyusage(Ext, UserState) of + valid -> + {valid, UserState}; + KeyUses -> + {fail, {bad_cert, {ca_invalid_ext_keyusage, KeyUses}}} + end; +validate(_, {extension, _}, UserState, _) -> {unknown, UserState}; -validate(Issuer, {bad_cert, cert_expired}, #{issuer := Issuer}, _LogLevel) -> +validate(Issuer, {bad_cert, cert_expired}, #{issuer := Issuer}, _) -> {fail, {bad_cert, root_cert_expired}}; -validate(_, {bad_cert, _} = Reason, _, _LogLevel) -> +validate(_, {bad_cert, _} = Reason, _, _) -> {fail, Reason}; -validate(Cert, valid, #{path_len := N} = UserState, LogLevel) -> - case verify_sign_support(Cert, UserState) of +validate(Cert, valid, UserState, LogLevel) -> + common_cert_validation(Cert, UserState, LogLevel); +validate(Cert, valid_peer, UserState0 = #{role := client, server_name := Hostname, + customize_hostname_check := Customize}, + LogLevel) when Hostname =/= disable -> + case verify_hostname(Hostname, Customize, Cert, UserState0) of + {valid, UserState} -> + common_cert_validation(Cert, UserState, LogLevel); + Error -> + Error + end; +validate(Cert, valid_peer, UserState, LogLevel) -> + common_cert_validation(Cert, UserState, LogLevel). + +common_cert_validation(Cert, #{path_len := N} = UserState, LogLevel) -> + case verify_sign_support(Cert, UserState) of true -> case maps:get(cert_ext, UserState, undefined) of undefined -> @@ -231,19 +248,7 @@ validate(Cert, valid, #{path_len := N} = UserState, LogLevel) -> end; false -> {fail, {bad_cert, unsupported_signature}} - end; -validate(Cert, valid_peer, UserState = #{role := client, server_name := Hostname, - customize_hostname_check := Customize}, - LogLevel) when Hostname =/= disable -> - case verify_hostname(Hostname, Customize, Cert, UserState) of - {valid, UserState} -> - validate(Cert, valid, UserState, LogLevel); - Error -> - Error - end; -validate(Cert, valid_peer, UserState, LogLevel) -> - validate(Cert, valid, UserState, LogLevel). - + end. %%-------------------------------------------------------------------- -spec is_valid_key_usage(list(), term()) -> boolean(). %% @@ -500,20 +505,35 @@ do_find_issuer(IssuerFun, CertDbHandle, CertDb) -> Return end. -is_valid_extkey_usage(KeyUse, true, #{role := Role}) when is_list(KeyUse) -> - is_valid_key_usage(KeyUse, ext_keysage(Role)); -is_valid_extkey_usage(KeyUse, true, #{role := Role}) -> - is_valid_key_usage([KeyUse], ext_keysage(Role)); -is_valid_extkey_usage(_, false, _) -> - false. +verify_extkeyusage( #'Extension'{extnValue = KeyUses}, UserState)-> + case is_valid_extkey_usage(KeyUses, UserState) of + true -> + valid; + false -> + KeyUses + end. + +is_valid_extkey_usage(KeyUses, #{path_len := PathNum, + role := Role, + allow_any_ca_purpose := Allow}) -> + case PathNum of + 1 -> %% Peer Cert + is_valid_key_usage(KeyUses, ext_keyusage(Role)); + _ -> %% CA cert + is_valid_key_usage(KeyUses, ext_keyusage(Role)) + orelse (Allow andalso ext_keyusage_includes_any(KeyUses)) + end. -ext_keysage(client) -> +ext_keyusage(client) -> %% Client wants to verify server ?'id-kp-serverAuth'; -ext_keysage(server) -> +ext_keyusage(server) -> %% Server wants to verify client ?'id-kp-clientAuth'. +ext_keyusage_includes_any(KeyUse) -> + lists:member(?anyExtendedKeyUsage, KeyUse). + verify_cert_signer(BinCert, SignerTBSCert) -> PublicKey = public_key(SignerTBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo), public_key:pkix_verify(BinCert, PublicKey). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 519bec936f42..99869c3db170 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -2178,8 +2178,20 @@ path_validation_alert({bad_cert, unknown_ca}, _, _) -> path_validation_alert({bad_cert, hostname_check_failed}, ServerName, #cert{otp = PeerCert}) -> SubjAltNames = subject_altnames(PeerCert), ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,{bad_cert, {hostname_check_failed, {requested, ServerName}, - {received, SubjAltNames}}}); -path_validation_alert(Reason, _, _) -> + {received, SubjAltNames}}}); +path_validation_alert({bad_cert, invalid_ext_keyusage}, _, _) -> %% Detected by public key + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, {invalid_ext_keyusage, + "CA cert purpose anyExtendedKeyUsage" + "and extended-key-usage extension marked critical is not allowed"}); +path_validation_alert({bad_cert, {invalid_ext_keyusage, ExtKeyUses}}, _, _) -> + Uses = extkey_oids_to_names(ExtKeyUses, []), + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, {invalid_ext_keyusage, Uses}); +path_validation_alert({bad_cert, {ca_invalid_ext_keyusage, ExtKeyUses}}, _, _) -> + Uses = extkey_oids_to_names(ExtKeyUses, []), + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, {ca_invalid_ext_keyusage, Uses}); +path_validation_alert({bad_cert, {key_usage_mismatch, _} = Reason}, _, _) -> + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, Reason); +path_validation_alert(Reason, _,_) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason). digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) -> @@ -3900,6 +3912,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR #{verify_fun := VerifyFun, customize_hostname_check := CustomizeHostnameCheck, crl_check := CrlCheck, + allow_any_ca_purpose := AllowAnyPurpose, log_level := Level} = Opts, #{cert_ext := CertExt, stapling_state := StaplingState}) -> @@ -3920,6 +3933,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR cert_ext => CertExt, issuer => TrustedCert, stapling_state => StaplingState, + allow_any_ca_purpose => AllowAnyPurpose, path_len => length(Path) }, Path, Level), @@ -3962,6 +3976,25 @@ extensions_list(asn1_NOVALUE) -> extensions_list(Extensions) -> Extensions. +extkey_oids_to_names([], Acc) -> + Acc; +extkey_oids_to_names([?'id-kp-serverAuth'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["id-kp-serverAuth"| Acc]); +extkey_oids_to_names([?'id-kp-clientAuth'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["id-kp-clientAuth"| Acc]); +extkey_oids_to_names([?'id-kp-OCSPSigning'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["id-kp-OCSPSigning"| Acc]); +extkey_oids_to_names([?'id-kp-timeStamping'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["id-kp-emailProtection"| Acc]); +extkey_oids_to_names([?'id-kp-emailProtection'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["id-kp-emailProtection"| Acc]); +extkey_oids_to_names([?'id-kp-codeSigning'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["id-kp-codeSigning"| Acc]); +extkey_oids_to_names([?'anyExtendedKeyUsage'| Rest], Acc) -> + extkey_oids_to_names(Rest, ["anyExtendedKeyUsage"| Acc]); +extkey_oids_to_names([Other| Rest], Acc) -> + extkey_oids_to_names(Rest, [Other | Acc]). + %%%################################################################ %%%# %%%# Tracing diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 2bd286deb985..776e2a580872 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -1887,6 +1887,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR customize_hostname_check := CustomizeHostnameCheck, crl_check := CrlCheck, log_level := LogLevel, + allow_any_ca_purpose := AllowAnyPurpose, signature_algs := SignAlgos, signature_algs_cert := SignAlgosCert} = Opts, #{cert_ext := CertExt, @@ -1908,6 +1909,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR issuer => TrustedCert, cert_ext => CertExt, stapling_state => StaplingState, + allow_any_ca_purpose => AllowAnyPurpose, path_len => length(Path) }, Path, LogLevel), diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 121ae9c6d897..2e385361d7b1 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -2973,6 +2973,13 @@ options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_ {verify_fun, {NewF3, bar}}], client, DefOpts), + ?OK(#{allow_any_ca_purpose := true}, + [{allow_any_ca_purpose, true}, {verify, verify_peer}, {cacerts, [Cert]}], + server), + ?OK(#{allow_any_ca_purpose := false}, + [{verify, verify_peer}, {cacerts, [Cert]}], + client), + %% Errors ?ERR({partial_chain, undefined}, [{partial_chain, undefined}], client), ?ERR({options, incompatible, [{verify, verify_none}, {fail_if_no_peer_cert, true}]}, @@ -2994,6 +3001,8 @@ options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_ client), ?ERR({cert_policy_opts, {inhibit_any_policy, bar}}, [{verify, verify_peer}, {cacerts, [Cert]}, {cert_policy_opts, [{inhibit_any_policy,bar}]}], client), + ?ERR({middlebox_comp_mode, foo}, [{middlebox_comp_mode, foo}], server), + ?ERR({allow_any_ca_purpose, foo},[{allow_any_ca_purpose, foo}, {verify, verify_peer}, {cacerts, [Cert]}], server), ok. options_fallback(_Config) -> diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl index 2b6ea0d434ce..623ccf913cd4 100644 --- a/lib/ssl/test/ssl_cert_SUITE.erl +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -93,6 +93,16 @@ extended_key_usage_auth/1, extended_key_usage_client_auth/0, extended_key_usage_client_auth/1, + extended_key_usage_mixup_client/0, + extended_key_usage_mixup_client/1, + extended_key_usage_mixup_server/0, + extended_key_usage_mixup_server/1, + extended_key_usage_ca/0, + extended_key_usage_ca/1, + extended_key_usage_ca_invalid/0, + extended_key_usage_ca_invalid/1, + extended_key_usage_ca_any/0, + extended_key_usage_ca_any/1, cert_expired/0, cert_expired/1, no_auth_key_identifier_ext/0, @@ -256,6 +266,11 @@ all_version_tests() -> critical_extension_no_auth, extended_key_usage_auth, extended_key_usage_client_auth, + extended_key_usage_mixup_client, + extended_key_usage_mixup_server, + extended_key_usage_ca, + extended_key_usage_ca_invalid, + extended_key_usage_ca_any, cert_expired, no_auth_key_identifier_ext, no_auth_key_identifier_ext_keyEncipherment @@ -870,33 +885,14 @@ extended_key_usage_auth(Config) when is_list(Config) -> DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)), Ext = x509_test:extensions([{?'id-ce-extKeyUsage', [?'id-kp-serverAuth'], true}]), - #{client_config := ClientOpts0, - server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), - [{server_chain, - [[],[], [{extensions, Ext}]]}, - {client_chain, DefaultCertConf} - ]), - ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config), - ServerOpts = ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Version = proplists:get_value(version, Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{verify, verify_peer} | - ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + #{client_config := ClientOpts, + server_config := ServerOpts} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{server_chain, + [[],[], [{extensions, Ext}]]}, + {client_chain, DefaultCertConf} + ]), + positive_extended_keyusage(ClientOpts, ServerOpts, Config). %%-------------------------------------------------------------------- extended_key_usage_client_auth() -> @@ -907,30 +903,134 @@ extended_key_usage_client_auth(Config) when is_list(Config) -> [?'id-kp-serverAuth'], true}]), ClientExt = x509_test:extensions([{?'id-ce-extKeyUsage', [?'id-kp-clientAuth'], true}]), - #{client_config := ClientOpts0, - server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), - [{client_chain, [[],[],[{extensions, ClientExt}]]}, - {server_chain, [[],[],[{extensions, ServerExt}]]}]), - ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config), - ServerOpts = ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config), + #{client_config := ClientOpts, + server_config := ServerOpts} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[],[{extensions, ClientExt}]]}, + {server_chain, [[],[],[{extensions, ServerExt}]]}]), + + positive_extended_keyusage(ClientOpts, ServerOpts, Config). - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Version = proplists:get_value(version, Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{verify, verify_peer} | ClientOpts]}]), +%%-------------------------------------------------------------------- +extended_key_usage_mixup_server() -> + [{doc,"Test cert extended_key_usage extension is always verified by having server use client extension"}]. + +extended_key_usage_mixup_server(Config) when is_list(Config) -> + ClientExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-clientAuth'], false}]), + #{client_config := ClientOpts, + server_config := ServerOpts} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[],[{extensions, ClientExt}]]}, + {server_chain, [[],[],[{extensions, ClientExt}]]}]), + + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[],[{extensions, ClientExt}]]}, + {server_chain, [[],[],[{extensions, ClientExt}]]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + negative_extended_keyusage(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- + +extended_key_usage_mixup_client() -> + [{doc,"Test cert extended_key_usage extension is always verified by having client use server extension"}]. + +extended_key_usage_mixup_client(Config) when is_list(Config) -> + ServerExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-serverAuth'], false}]), + + #{client_config := ClientOpts, + server_config := ServerOpts} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[],[{extensions, ServerExt}]]}, + {server_chain, [[],[],[{extensions, ServerExt}]]}]), + negative_extended_keyusage(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +extended_key_usage_ca() -> + [{doc,"Test extended key usage in CA cert"}]. + +extended_key_usage_ca(Config) when is_list(Config) -> + ServerExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-serverAuth'], true}]), + ClientExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-clientAuth'], true}]), + CAExt = x509_test:extensions([{?'id-ce-keyUsage', + [keyCertSign, digitalSignature], true}]), + + #{client_config := ClientOpts0, + server_config := ServerOpts0} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[{extensions, CAExt ++ ClientExt}], + [{extensions, ClientExt}]]}, + {server_chain, [[],[{extensions, CAExt ++ ServerExt}], + [{extensions, ServerExt}]]}]), + + + positive_extended_keyusage(ClientOpts0, ServerOpts0, Config), + + CAExt1 = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-OCSPSigning', ?'id-kp-serverAuth'], true}]), + + #{client_config := ClientOpts1, + server_config := ServerOpts1} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[{extensions, CAExt ++ ClientExt}], + [{extensions, ClientExt}]]}, + {server_chain, [[],[{extensions, CAExt ++ CAExt1}], + [{extensions, ServerExt}]]}]), + + positive_extended_keyusage(ClientOpts1, ServerOpts1, Config). + +%%-------------------------------------------------------------------- +extended_key_usage_ca_invalid() -> + [{doc,"Test extended key usage in CA cert that will not validate"}]. + +extended_key_usage_ca_invalid(Config) when is_list(Config) -> + ServerExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-serverAuth'], true}]), + ClientExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-clientAuth'], true}]), + CAExt = x509_test:extensions([{?'id-ce-keyUsage', [keyCertSign, cRLSign], false}, + {?'id-ce-extKeyUsage', + [?'id-kp-OCSPSigning'], true}]), + + #{client_config := ClientOpts, + server_config := ServerOpts} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[{extensions, ClientExt}], + [{extensions, ClientExt}]]}, + {server_chain, [[],[{extensions, CAExt ++ ServerExt}], + [{extensions, ServerExt}]]}]), + + negative_extended_keyusage(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- + +extended_key_usage_ca_any() -> + [{doc,"Test extended key usage in CA cert"}]. + +extended_key_usage_ca_any(Config) when is_list(Config) -> + ServerExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-serverAuth'], true}]), + ClientExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?'id-kp-clientAuth'], true}]), + CAExt = x509_test:extensions([{?'id-ce-extKeyUsage', + [?anyExtendedKeyUsage, ?'id-kp-OCSPSigning'], false}]), + + #{client_config := ClientOpts, + server_config := ServerOpts} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[], + [{extensions, ClientExt}]]}, + {server_chain, [[],[{extensions, CAExt}], + [{extensions, ServerExt}]]}]), + + positive_extended_keyusage([{allow_any_ca_purpose, true} | ClientOpts], + ServerOpts, Config), + + negative_extended_keyusage(ClientOpts, ServerOpts, Config). %%-------------------------------------------------------------------- cert_expired() -> @@ -1426,3 +1526,48 @@ chain_and_root(Config) -> {ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, [], encoded), {Chain, Root}. + +positive_extended_keyusage(ClientOpts0, ServerOpts0, Config) -> + ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Version = proplists:get_value(version, Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, no_reuse(ssl_test_lib:n_version(Version)) ++ + [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +negative_extended_keyusage(ClientOpts0, ServerOpts0, Config) -> + ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Version = proplists:get_value(version, Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{verify, verify_peer}, + {fail_if_no_peer_cert, true}] ++ + no_reuse(ssl_test_lib:n_version(Version)) ++ ServerOpts + }]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, unsupported_certificate).