From 8d058f5f1928b743a669fcd6a1d3d12def60d404 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Mon, 11 Nov 2024 11:09:08 +0100 Subject: [PATCH] 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/doc/src/ssl.xml | 23 +++ lib/ssl/src/ssl.erl | 34 +++-- lib/ssl/src/ssl_certificate.erl | 83 +++++++---- lib/ssl/src/ssl_handshake.erl | 37 ++++- lib/ssl/src/tls_handshake_1_3.erl | 2 + lib/ssl/test/ssl_api_SUITE.erl | 8 + lib/ssl/test/ssl_cert_SUITE.erl | 238 ++++++++++++++++++++++++------ 7 files changed, 334 insertions(+), 91 deletions(-) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index e302324bf9ad..6474f9895dc6 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -716,6 +716,18 @@ Types: selfsigned_peer

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 @@ -723,6 +735,17 @@ marker="public_key:public_key#pkix_path_validation/3">public_key:pkix_path_valid + + + + +

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 the CA that includes this purpose, the option default to false.

+ + + diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index b26a4b91f0cb..a29047a41003 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -352,6 +352,7 @@ {keep_secrets, keep_secrets()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | + {allow_any_ca_purpose, allow_any_ca_purpose()} | {crl_check, crl_check()} | {crl_cache, crl_cache_opts()} | {max_handshake_size, handshake_size()} | @@ -398,6 +399,7 @@ -type allowed_cert_chain_length() :: integer(). -type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: any()}. +-type allow_any_ca_purpose() :: boolean(). -type crl_check() :: boolean() | peer | best_effort. -type crl_cache_opts() :: {Module :: atom(), {DbHandle :: internal | term(), @@ -1680,6 +1682,7 @@ ssl_options() -> use_srtp, user_lookup_fun, verify, verify_fun, + allow_any_ca_purpose, versions ]. @@ -1840,17 +1843,18 @@ opt_verification(UserOpts, Opts0, #{role := Role} = Env) -> option_incompatible(FailNoPeerCert andalso Verify =:= verify_none, [{verify, verify_none}, {fail_if_no_peer_cert, true}]), - Opts = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2), + Opts3 = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2), - case Role of - client -> - opt_verify_fun(UserOpts, Opts#{partial_chain => PartialChain}, - Env); - server -> - opt_verify_fun(UserOpts, Opts#{partial_chain => PartialChain, - fail_if_no_peer_cert => FailNoPeerCert}, - Env) - end. + Opts = case Role of + client -> + opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain}, + Env); + server -> + opt_verify_fun(UserOpts, Opts3#{partial_chain => PartialChain, + fail_if_no_peer_cert => FailNoPeerCert}, + Env) + end, + opt_extend_keyusage(UserOpts, Opts). default_verify(client) -> %% Server authenication is by default requiered @@ -1904,6 +1908,16 @@ convert_verify_fun() -> {valid, UserState} 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. + opt_certs(UserOpts, #{log_level := LogLevel} = Opts0, Env) -> case get_opt_list(certs_keys, [], UserOpts, Opts0) of {Where, []} when Where =/= new -> diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 527a9d37f492..265d74d45e3a 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -205,24 +205,41 @@ 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) -> - %% 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, #'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}) -> {fail, {bad_cert, root_cert_expired}}; validate(_, {bad_cert, _} = Reason, _) -> {fail, Reason}; -validate(Cert, valid, #{path_len := N} = UserState) -> - case verify_sign_support(Cert, UserState) of +validate(Cert, valid, UserState) -> + common_cert_validation(Cert, UserState); +validate(Cert, valid_peer, UserState0 = #{role := client, server_name := Hostname, + customize_hostname_check := Customize}) when Hostname =/= disable -> + case verify_hostname(Hostname, Customize, Cert, UserState0) of + {valid, UserState} -> + common_cert_validation(Cert, UserState); + Error -> + Error + end; +validate(Cert, valid_peer, UserState) -> + common_cert_validation(Cert, UserState). + +common_cert_validation(Cert, #{path_len := N} = UserState) -> + case verify_sign_support(Cert, UserState) of true -> case maps:get(cert_ext, UserState, undefined) of undefined -> @@ -232,18 +249,7 @@ validate(Cert, valid, #{path_len := N} = UserState) -> end; false -> {fail, {bad_cert, unsupported_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). - + end. %%-------------------------------------------------------------------- -spec is_valid_key_usage(list(), term()) -> boolean(). %% @@ -500,20 +506,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 699b8a4e2909..e7125194c2bd 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -2143,8 +2143,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) -> @@ -3856,6 +3868,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, ocsp_responder_certs := OcspResponderCerts, @@ -3878,6 +3891,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR issuer => TrustedCert, ocsp_responder_certs => OcspResponderCerts, ocsp_state => OcspState, + allow_any_ca_purpose => AllowAnyPurpose, path_len => length(Path) }, Path, Level), @@ -3921,6 +3935,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 d3b240d2eafe..5a8d04d1a7db 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -1885,6 +1885,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 cert_ext => CertExt, ocsp_responder_certs => OcspResponderCerts, ocsp_state => OcspState, + 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 27ca87aac1bd..c90151a7aafe 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -2837,6 +2837,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}]}, @@ -2849,6 +2856,7 @@ options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_ ?ERR({options, incompatible, [{verify, _}, {cacerts, undefined}]}, [{verify, verify_peer}], server), ?ERR({partial_chain, not_a_fun}, [{partial_chain, not_a_fun}], client), ?ERR({verify_fun, not_a_fun}, [{verify_fun, not_a_fun}], client), + ?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 119f209c6876..a009e38bb9c9 100644 --- a/lib/ssl/test/ssl_cert_SUITE.erl +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -91,6 +91,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 @@ -865,33 +880,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() -> @@ -902,30 +898,131 @@ 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}]), + #{client_config := ClientOpts0, + server_config := ServerOpts0} = + ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{client_chain, [[],[{extensions, ClientExt}], + [{extensions, ClientExt}]]}, + {server_chain, [[],[{extensions, ServerExt}], + [{extensions, ServerExt}]]}]), + + + positive_extended_keyusage(ClientOpts0, ServerOpts0, Config), + + CAExt = 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, ClientExt}], + [{extensions, ClientExt}]]}, + {server_chain, [[],[{extensions, CAExt}], + [{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() -> @@ -1432,3 +1529,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).