From 1664ea4ad4a0ad309fad3e7f5d730329b5a91536 Mon Sep 17 00:00:00 2001 From: William Yang Date: Thu, 13 Jun 2024 13:31:58 +0200 Subject: [PATCH] Revert: TLS partial-chain and keyUsage #12955 #12977 This reverts commit 28b17a25624409553c50cf5c31e79985f7cae376. This reverts commit 01467246fc253f3c64b078d19059ca92c78cea1a. This reverts commit c3f8ba57623cdb9c4439f7894d1c2fac4c3dcedb. This reverts commit 1a4a4bb3a53c72c7d52688c5ed59086f5d7e6b8f. This reverts commit fb30207ef3ca1e2c86c813ffacc489f35a5dd958. This reverts commit 337c230e79a6f53eba7dbec503940c12a478fc00. This reverts commit 3a674f44f12a5e2e3e6aa7c41609bc3e41e1815b. This reverts commit 70ffd77f995e05c0eb6821f709f4769c392e614b. This reverts commit 03b093556472fe763531c655f35db990999c413d. This reverts commit 650cf4b27ea50bd93590bfdaa629c5a207bf8990. This reverts commit 43ad665dcfdd354bc9a717db00712a52890e7d6d. This reverts commit a29a43e5fc9e1d455893ed05468f13908c21af74. This reverts commit 4e9c1ec0c9ec22559866ae19a2a0e254addb2059. This reverts commit 8eb463c58d6f8548e3c4088b9c05cf187bb27d49. This reverts commit 90430fa66dca7d828858aa06ca03ce44c0cd2629. This reverts commit eb1ab9adfe86dc917260055d1e6080a4f29021fa. This reverts commit 8bc3a86f63315e090384b0137ddc49be1df74735. This reverts commit fa4357ce89e141fbbf41ad525ab34ea472fd138e. This reverts commit 0b95a08d32db66903ba8155d7e405c901f044427. --- apps/emqx/src/emqx_const_v2.erl | 125 ---- apps/emqx/src/emqx_listeners.erl | 28 +- apps/emqx/src/emqx_schema.erl | 16 - apps/emqx/src/emqx_tls_lib.erl | 55 -- .../emqx_listener_tls_verify_chain_SUITE.erl | 257 ------- ...mqx_listener_tls_verify_keyusage_SUITE.erl | 372 --------- ...istener_tls_verify_partial_chain_SUITE.erl | 708 ------------------ apps/emqx/test/emqx_test_tls_certs_helper.erl | 319 -------- apps/emqx_gateway/src/emqx_gateway_utils.erl | 8 - .../test/emqx_mgmt_api_configs_SUITE.erl | 1 - changes/ce/feat-11721.en.md | 22 - mix.exs | 3 +- rebar.config | 3 +- rel/i18n/emqx_schema.hocon | 43 -- scripts/spellcheck/dicts/emqx.txt | 1 - 15 files changed, 5 insertions(+), 1956 deletions(-) delete mode 100644 apps/emqx/src/emqx_const_v2.erl delete mode 100644 apps/emqx/test/emqx_listener_tls_verify_chain_SUITE.erl delete mode 100644 apps/emqx/test/emqx_listener_tls_verify_keyusage_SUITE.erl delete mode 100644 apps/emqx/test/emqx_listener_tls_verify_partial_chain_SUITE.erl delete mode 100644 apps/emqx/test/emqx_test_tls_certs_helper.erl delete mode 100644 changes/ce/feat-11721.en.md diff --git a/apps/emqx/src/emqx_const_v2.erl b/apps/emqx/src/emqx_const_v2.erl deleted file mode 100644 index 0d95cf43c..000000000 --- a/apps/emqx/src/emqx_const_v2.erl +++ /dev/null @@ -1,125 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% @doc Never update this module, create a v3 instead. -%%-------------------------------------------------------------------- - --module(emqx_const_v2). --elvis([{elvis_style, atom_naming_convention, #{regex => "^([a-z][a-z0-9A-Z]*_?)*(_SUITE)?$"}}]). - --export([ - make_tls_root_fun/2, - make_tls_verify_fun/2 -]). - --include_lib("public_key/include/public_key.hrl"). -%% @doc Build a root fun for verify TLS partial_chain. -%% The `InputChain' is composed by OTP SSL with local cert store -%% AND the cert (chain if any) from the client. -%% @end -make_tls_root_fun(cacert_from_cacertfile, [Trusted]) -> - %% Allow only one trusted ca cert, and just return the defined trusted CA cert, - fun(_InputChain) -> - %% Note, returing `trusted_ca` doesn't really mean it accepts the connection - %% OTP SSL app will do the path validation, signature validation subsequently. - {trusted_ca, Trusted} - end; -make_tls_root_fun(cacert_from_cacertfile, [TrustedOne, TrustedTwo]) -> - %% Allow two trusted CA certs in case of CA cert renewal - %% This is a little expensive call as it compares the binaries. - fun(InputChain) -> - case lists:member(TrustedOne, InputChain) of - true -> - {trusted_ca, TrustedOne}; - false -> - {trusted_ca, TrustedTwo} - end - end. - -make_tls_verify_fun(verify_cert_extKeyUsage, KeyUsages) -> - RequiredKeyUsages = ext_key_opts(KeyUsages), - {fun verify_fun_peer_extKeyUsage/3, RequiredKeyUsages}. - -verify_fun_peer_extKeyUsage(_, {bad_cert, invalid_ext_key_usage}, UserState) -> - %% !! Override OTP verify peer default - %% OTP SSL is unhappy with the ext_key_usage but we will check on our own. - {unknown, UserState}; -verify_fun_peer_extKeyUsage(_, {bad_cert, _} = Reason, _UserState) -> - %% OTP verify_peer default - {fail, Reason}; -verify_fun_peer_extKeyUsage(_, {extension, _}, UserState) -> - %% OTP verify_peer default - {unknown, UserState}; -verify_fun_peer_extKeyUsage(_, valid, UserState) -> - %% OTP verify_peer default - {valid, UserState}; -verify_fun_peer_extKeyUsage( - #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{extensions = ExtL}}, - %% valid peer cert - valid_peer, - RequiredKeyUsages -) -> - %% override OTP verify_peer default - %% must have id-ce-extKeyUsage - case lists:keyfind(?'id-ce-extKeyUsage', 2, ExtL) of - #'Extension'{extnID = ?'id-ce-extKeyUsage', extnValue = VL} -> - case do_verify_ext_key_usage(VL, RequiredKeyUsages) of - true -> - %% pass the check, - %% fallback to OTP verify_peer default - {valid, RequiredKeyUsages}; - false -> - {fail, extKeyUsage_unmatched} - end; - _ -> - {fail, extKeyUsage_not_set} - end. - -%% @doc check required extkeyUsages are presented in the cert -do_verify_ext_key_usage(_, []) -> - %% Verify finished - true; -do_verify_ext_key_usage(CertExtL, [Usage | T] = _Required) -> - case lists:member(Usage, CertExtL) of - true -> - do_verify_ext_key_usage(CertExtL, T); - false -> - false - end. - -%% @doc Helper tls cert extension --spec ext_key_opts(string()) -> [OidString :: string() | public_key:oid()]. -ext_key_opts(Str) -> - Usages = string:tokens(Str, ","), - lists:map( - fun - ("clientAuth") -> - ?'id-kp-clientAuth'; - ("serverAuth") -> - ?'id-kp-serverAuth'; - ("codeSigning") -> - ?'id-kp-codeSigning'; - ("emailProtection") -> - ?'id-kp-emailProtection'; - ("timeStamping") -> - ?'id-kp-timeStamping'; - ("ocspSigning") -> - ?'id-kp-OCSPSigning'; - ("OID:" ++ OidStr) -> - OidList = string:tokens(OidStr, "."), - list_to_tuple(lists:map(fun list_to_integer/1, OidList)) - end, - Usages - ). diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 122118c6d..dd9024fef 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -611,9 +611,7 @@ esockd_opts(ListenerId, Type, Name, Opts0) -> ssl -> OptsWithCRL = inject_crl_config(Opts0), OptsWithSNI = inject_sni_fun(ListenerId, OptsWithCRL), - OptsWithRootFun = inject_root_fun(OptsWithSNI), - OptsWithVerifyFun = inject_verify_fun(OptsWithRootFun), - SSLOpts = ssl_opts(OptsWithVerifyFun), + SSLOpts = ssl_opts(OptsWithSNI), Opts3#{ssl_options => SSLOpts, tcp_options => tcp_opts(Opts0)} end ). @@ -637,18 +635,8 @@ ranch_opts(Type, Opts = #{bind := ListenOn}) -> MaxConnections = maps:get(max_connections, Opts, 1024), SocketOpts = case Type of - wss -> - tcp_opts(Opts) ++ - lists:filter( - fun - ({partial_chain, _}) -> false; - ({handshake_timeout, _}) -> false; - (_) -> true - end, - ssl_opts(Opts) - ); - ws -> - tcp_opts(Opts) + wss -> tcp_opts(Opts) ++ proplists:delete(handshake_timeout, ssl_opts(Opts)); + ws -> tcp_opts(Opts) end, #{ num_acceptors => NumAcceptors, @@ -974,16 +962,6 @@ quic_listener_optional_settings() -> stateless_operation_expiration_ms ]. -inject_root_fun(#{ssl_options := SslOpts} = Opts) -> - Opts#{ssl_options := emqx_tls_lib:opt_partial_chain(SslOpts)}; -inject_root_fun(Opts) -> - Opts. - -inject_verify_fun(#{ssl_options := SslOpts} = Opts) -> - Opts#{ssl_options := emqx_tls_lib:opt_verify_fun(SslOpts)}; -inject_verify_fun(Opts) -> - Opts. - inject_sni_fun(ListenerId, Conf = #{ssl_options := #{ocsp := #{enable_ocsp_stapling := true}}}) -> emqx_ocsp_cache:inject_sni_fun(ListenerId, Conf); inject_sni_fun(_ListenerId, Conf) -> diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 6c8466aab..a83a13209 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -2178,22 +2178,6 @@ common_ssl_opts_schema(Defaults, Type) -> desc => ?DESC(common_ssl_opts_schema_verify) } )}, - {"partial_chain", - sc( - hoconsc:enum([true, false, two_cacerts_from_cacertfile, cacert_from_cacertfile]), - #{ - default => Df(partial_chain, false), - desc => ?DESC(common_ssl_opts_schema_partial_chain) - } - )}, - {"verify_peer_ext_key_usage", - sc( - string(), - #{ - required => false, - desc => ?DESC(common_ssl_opts_verify_peer_ext_key_usage) - } - )}, {"reuse_sessions", sc( boolean(), diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index 09a846832..c524381ad 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -15,7 +15,6 @@ %%-------------------------------------------------------------------- -module(emqx_tls_lib). --elvis([{elvis_style, atom_naming_convention, #{regex => "^([a-z][a-z0-9A-Z]*_?)*(_SUITE)?$"}}]). %% version & cipher suites -export([ @@ -24,8 +23,6 @@ default_ciphers/0, selected_ciphers/1, integral_ciphers/2, - opt_partial_chain/1, - opt_verify_fun/1, all_ciphers_set_cached/0 ]). @@ -688,55 +685,3 @@ ensure_ssl_file_key(SSL, RequiredKeyPaths) -> [] -> ok; Miss -> {error, #{reason => ssl_file_option_not_found, which_options => Miss}} end. - -%% @doc enable TLS partial_chain validation if set. --spec opt_partial_chain(SslOpts :: map()) -> NewSslOpts :: map(). -opt_partial_chain(#{partial_chain := false} = SslOpts) -> - maps:remove(partial_chain, SslOpts); -opt_partial_chain(#{partial_chain := true} = SslOpts) -> - SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(1, SslOpts)}; -opt_partial_chain(#{partial_chain := cacert_from_cacertfile} = SslOpts) -> - SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(1, SslOpts)}; -opt_partial_chain(#{partial_chain := two_cacerts_from_cacertfile} = SslOpts) -> - SslOpts#{partial_chain := rootfun_trusted_ca_from_cacertfile(2, SslOpts)}; -opt_partial_chain(SslOpts) -> - SslOpts. - -%% @doc make verify_fun if set. --spec opt_verify_fun(SslOpts :: map()) -> NewSslOpts :: map(). -opt_verify_fun(#{verify_peer_ext_key_usage := V} = SslOpts) when V =/= undefined -> - SslOpts#{verify_fun => emqx_const_v2:make_tls_verify_fun(verify_cert_extKeyUsage, V)}; -opt_verify_fun(SslOpts) -> - SslOpts. - -%% @doc Helper, make TLS root_fun -rootfun_trusted_ca_from_cacertfile(NumOfCerts, #{cacertfile := Cacertfile}) -> - case file:read_file(Cacertfile) of - {ok, PemBin} -> - try - do_rootfun_trusted_ca_from_cacertfile(NumOfCerts, PemBin) - catch - _Error:_Info:ST -> - %% The cacertfile will be checked by OTP SSL as well and OTP choice to be silent on this. - %% We are touching security sutffs, don't leak extra info.. - ?SLOG(error, #{ - msg => "trusted_cacert_not_found_in_cacertfile", stacktrace => ST - }), - throw({error, ?FUNCTION_NAME}) - end; - {error, Reason} -> - throw({error, {read_cacertfile_error, Cacertfile, Reason}}) - end; -rootfun_trusted_ca_from_cacertfile(_NumOfCerts, _SslOpts) -> - throw({error, cacertfile_unset}). - -do_rootfun_trusted_ca_from_cacertfile(NumOfCerts, PemBin) -> - %% The last one or two should be the top parent in the chain if it is a chain - Certs = public_key:pem_decode(PemBin), - Pos = length(Certs) - NumOfCerts + 1, - Trusted = [ - CADer - || {'Certificate', CADer, _} <- - lists:sublist(public_key:pem_decode(PemBin), Pos, NumOfCerts) - ], - emqx_const_v2:make_tls_root_fun(cacert_from_cacertfile, Trusted). diff --git a/apps/emqx/test/emqx_listener_tls_verify_chain_SUITE.erl b/apps/emqx/test/emqx_listener_tls_verify_chain_SUITE.erl deleted file mode 100644 index 0b445c939..000000000 --- a/apps/emqx/test/emqx_listener_tls_verify_chain_SUITE.erl +++ /dev/null @@ -1,257 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- --module(emqx_listener_tls_verify_chain_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - --import( - emqx_test_tls_certs_helper, - [ - emqx_start_listener/4, - fail_when_ssl_error/1, - fail_when_no_ssl_alert/2, - generate_tls_certs/1 - ] -). - -all() -> emqx_common_test_helpers:all(?MODULE). - -init_per_suite(Config) -> - generate_tls_certs(Config), - application:ensure_all_started(esockd), - [{ssl_config, ssl_config_verify_peer()} | Config]. - -end_per_suite(_Config) -> - application:stop(esockd). - -t_conn_fail_with_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")}, - {verify, verify_none} - ], - 1000 - ), - - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_conn_fail_with_other_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2.pem")}, - {verify, verify_none} - ], - 1000 - ), - - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_conn_success_with_server_client_composed_complete_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Server has root ca cert - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "root.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - | ?config(ssl_config, Config) - ]} - ], - %% Client has complete chain - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")}, - {verify, verify_none} - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_with_other_signed_client_composed_complete_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Server has root ca cert - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "root.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - | ?config(ssl_config, Config) - ]} - ], - %% Client has partial_chain - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")}, - {verify, verify_none} - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_with_renewed_intermediate_root_bundle(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Server has root ca cert - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "intermediate1_renewed-root-bundle.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")}, - {verify, verify_none} - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_with_client_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "root.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")}, - {verify, verify_none} - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_fail_with_server_partial_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% imcomplete at server side - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "intermediate2.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")}, - {versions, ['tlsv1.2']}, - {verify, verify_none} - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_without_root_cacert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, [ - {cacertfile, filename:join(DataDir, "intermediate2.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")}, - %% stick to tlsv1.2 for consistent error message - {versions, ['tlsv1.2']}, - {cacertfile, filename:join(DataDir, "intermediate2.pem")} - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -ssl_config_verify_peer() -> - [ - {verify, verify_peer}, - {fail_if_no_peer_cert, true} - ]. diff --git a/apps/emqx/test/emqx_listener_tls_verify_keyusage_SUITE.erl b/apps/emqx/test/emqx_listener_tls_verify_keyusage_SUITE.erl deleted file mode 100644 index 8265a7492..000000000 --- a/apps/emqx/test/emqx_listener_tls_verify_keyusage_SUITE.erl +++ /dev/null @@ -1,372 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- --module(emqx_listener_tls_verify_keyusage_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - --import( - emqx_test_tls_certs_helper, - [ - fail_when_ssl_error/1, - fail_when_no_ssl_alert/2, - generate_tls_certs/1, - gen_host_cert/4, - emqx_start_listener/4 - ] -). - -all() -> - [ - {group, full_chain}, - {group, partial_chain} - ]. - -all_tc() -> - emqx_common_test_helpers:all(?MODULE). - -groups() -> - [ - {partial_chain, [], all_tc()}, - {full_chain, [], all_tc()} - ]. - -init_per_suite(Config) -> - generate_tls_certs(Config), - application:ensure_all_started(esockd), - Config. - -end_per_suite(_Config) -> - application:stop(esockd). - -init_per_group(full_chain, Config) -> - [{ssl_config, ssl_config_verify_peer_full_chain(Config)} | Config]; -init_per_group(partial_chain, Config) -> - [{ssl_config, ssl_config_verify_peer_partial_chain(Config)} | Config]; -init_per_group(_, Config) -> - Config. - -end_per_group(_, Config) -> - Config. - -t_conn_success_verify_peer_ext_key_usage_unset(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Given listener keyusage unset - Options = [{ssl_options, ?config(ssl_config, Config)}], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% when client connect with cert without keyusage ext - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection success - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_verify_peer_ext_key_usage_undefined(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Give listener keyusage is set to undefined - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, undefined} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% when client connect with cert without keyusages ext - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection success - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_verify_peer_ext_key_usage_matched_predefined(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Give listener keyusage is set to clientAuth - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, "clientAuth"} - | ?config(ssl_config, Config) - ]} - ], - - %% When client cert has clientAuth that is matched - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "clientAuth"), - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, client_key_file(DataDir, ?FUNCTION_NAME)}, - {certfile, client_pem_file(DataDir, ?FUNCTION_NAME)}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection success - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_verify_peer_ext_key_usage_matched_raw_oid(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Give listener keyusage is set to raw OID - - %% from OTP-PUB-KEY.hrl - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, "OID:1.3.6.1.5.5.7.3.2"} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% When client cert has keyusage and matched. - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "clientAuth"), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, client_key_file(DataDir, ?FUNCTION_NAME)}, - {certfile, client_pem_file(DataDir, ?FUNCTION_NAME)}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection success - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_verify_peer_ext_key_usage_matched_ordered_list(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - - %% Give listener keyusage is clientAuth,serverAuth - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, "clientAuth,serverAuth"} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% When client cert has the same keyusage ext list - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "clientAuth,serverAuth"), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, client_key_file(DataDir, ?FUNCTION_NAME)}, - {certfile, client_pem_file(DataDir, ?FUNCTION_NAME)}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection success - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_verify_peer_ext_key_usage_matched_unordered_list(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Give listener keyusage is clientAuth,serverAuth - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, "serverAuth,clientAuth"} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% When client cert has the same keyusage ext list but different order - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "clientAuth,serverAuth"), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, client_key_file(DataDir, ?FUNCTION_NAME)}, - {certfile, client_pem_file(DataDir, ?FUNCTION_NAME)}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection success - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_fail_verify_peer_ext_key_usage_unmatched_raw_oid(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Give listener keyusage is using OID - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, "OID:1.3.6.1.5.5.7.3.1"} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - - %% When client cert has the keyusage but not matching OID - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "clientAuth"), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, client_key_file(DataDir, ?FUNCTION_NAME)}, - {certfile, client_pem_file(DataDir, ?FUNCTION_NAME)}, - {verify, verify_none} - ], - 1000 - ), - - %% Then connecion should fail. - fail_when_no_ssl_alert(Socket, handshake_failure), - ok = ssl:close(Socket). - -t_conn_fail_verify_peer_ext_key_usage_empty_str(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, ""} - | ?config(ssl_config, Config) - ]} - ], - %% Give listener keyusage is empty string - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% When client connect with cert without keyusage - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")}, - {verify, verify_none} - ], - 1000 - ), - %% Then connecion should fail. - fail_when_no_ssl_alert(Socket, handshake_failure), - ok = ssl:close(Socket). - -t_conn_fail_client_keyusage_unmatch(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - - %% Give listener keyusage is clientAuth - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, "clientAuth"} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% When client connect with mismatch cert keyusage = codeSigning - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "codeSigning"), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, client_key_file(DataDir, ?FUNCTION_NAME)}, - {certfile, client_pem_file(DataDir, ?FUNCTION_NAME)}, - {verify, verify_none} - ], - 1000 - ), - %% Then connecion should fail. - fail_when_no_ssl_alert(Socket, handshake_failure), - ok = ssl:close(Socket). - -t_conn_fail_client_keyusage_incomplete(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Give listener keyusage is codeSigning,clientAuth - Options = [ - {ssl_options, [ - {verify_peer_ext_key_usage, - "serverAuth,clientAuth,codeSigning,emailProtection,timeStamping,ocspSigning"} - | ?config(ssl_config, Config) - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - %% When client connect with cert keyusage = clientAuth - gen_client_cert_ext_keyusage(?FUNCTION_NAME, "intermediate1", DataDir, "codeSigning"), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")}, - {verify, verify_none} - ], - 1000 - ), - %% Then connection should fail - fail_when_no_ssl_alert(Socket, handshake_failure), - ok = ssl:close(Socket). - -%%% -%%% Helpers -%%% -gen_client_cert_ext_keyusage(Name, CA, DataDir, Usage) when is_atom(Name) -> - gen_client_cert_ext_keyusage(atom_to_list(Name), CA, DataDir, Usage); -gen_client_cert_ext_keyusage(Name, CA, DataDir, Usage) -> - gen_host_cert(Name, CA, DataDir, #{ext => "extendedKeyUsage=" ++ Usage}). - -client_key_file(DataDir, Name) -> - filename:join(DataDir, Name) ++ ".key". - -client_pem_file(DataDir, Name) -> - filename:join(DataDir, Name) ++ ".pem". - -ssl_config_verify_peer_full_chain(Config) -> - [ - {cacertfile, filename:join(?config(data_dir, Config), "intermediate1-root-bundle.pem")} - | ssl_config_verify_peer(Config) - ]. -ssl_config_verify_peer_partial_chain(Config) -> - [ - {cacertfile, filename:join(?config(data_dir, Config), "intermediate1.pem")}, - {partial_chain, true} - | ssl_config_verify_peer(Config) - ]. - -ssl_config_verify_peer(Config) -> - DataDir = ?config(data_dir, Config), - [ - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {keyfile, filename:join(DataDir, "server1.key")}, - {certfile, filename:join(DataDir, "server1.pem")} - %% , {log_level, debug} - ]. diff --git a/apps/emqx/test/emqx_listener_tls_verify_partial_chain_SUITE.erl b/apps/emqx/test/emqx_listener_tls_verify_partial_chain_SUITE.erl deleted file mode 100644 index 1a1963dc9..000000000 --- a/apps/emqx/test/emqx_listener_tls_verify_partial_chain_SUITE.erl +++ /dev/null @@ -1,708 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- --module(emqx_listener_tls_verify_partial_chain_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - --import( - emqx_test_tls_certs_helper, - [ - emqx_start_listener/4, - fail_when_ssl_error/1, - fail_when_no_ssl_alert/2, - generate_tls_certs/1 - ] -). - -all() -> emqx_common_test_helpers:all(?MODULE). - -init_per_suite(Config) -> - generate_tls_certs(Config), - application:ensure_all_started(esockd), - [{ssl_config, ssl_config_verify_partial_chain()} | Config]. - -end_per_suite(_Config) -> - application:stop(esockd). - -t_conn_success_with_server_intermediate_cacert_and_client_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -t_conn_success_with_intermediate_cacert_bundle(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "server1-intermediate1-bundle.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -t_conn_success_with_renewed_intermediate_cacert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1_renewed.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -t_conn_fail_with_renewed_intermediate_cacert_and_client_using_old_complete_bundle(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_with_renewed_intermediate_cacert_and_client_using_old_bundle(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_success_with_old_and_renewed_intermediate_cacert_and_client_provides_renewed_client_cert( - Config -) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed_old-bundle.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")}, - {partial_chain, two_cacerts_from_cacertfile} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2_renewed.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -%% Note, this is good to have for usecase coverage -t_conn_success_with_new_intermediate_cacert_and_client_provides_renewed_client_cert_signed_by_old_intermediate( - Config -) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2_renewed.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -%% @doc server should build a partial_chain with old version of ca cert. -t_conn_success_with_old_and_renewed_intermediate_cacert_and_client_provides_client_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed_old-bundle.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")}, - {partial_chain, two_cacerts_from_cacertfile} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -%% @doc verify when config does not allow two versions of certs from same trusted CA. -t_conn_fail_with_renewed_and_old_intermediate_cacert_and_client_using_old_bundle(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed_old-bundle.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -%% @doc verify when config (two_cacerts_from_cacertfile) allows two versions of certs from same trusted CA. -t_001_conn_success_with_old_and_renewed_intermediate_cacert_bundle_and_client_using_old_bundle( - Config -) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed_old-bundle.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")}, - {partial_chain, two_cacerts_from_cacertfile} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -%% @doc: verify even if listener has old/new intermediate2 certs, -%% client1 should not able to connect with old intermediate2 cert. -%% In this case, listener verify_fun returns {trusted_ca, Oldintermediate2Cert} but -%% OTP should still fail the validation since the client1 cert is not signed by -%% Oldintermediate2Cert (trusted CA cert). -%% @end -t_conn_fail_with_old_and_renewed_intermediate_cacert_bundle_and_client_using_all_CAcerts(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2_renewed_old-bundle.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")}, - {partial_chain, two_cacerts_from_cacertfile} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "all-CAcerts-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_with_renewed_intermediate_cacert_other_client(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1_renewed.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_with_intermediate_cacert_bundle_but_incorrect_order(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1-server1-bundle.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_when_singed_by_other_intermediate_ca(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_success_with_complete_chain_that_server_root_cacert_and_client_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "root.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_fail_with_other_client_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_with_server_intermediate_and_other_client_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1-root-bundle.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_success_with_server_intermediate_cacert_and_client_complete_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-complete-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_fail_with_server_intermediate_chain_and_client_other_incomplete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-intermediate2-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_with_server_intermediate_and_other_client_root_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-root-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_success_with_server_intermediate_and_client_root_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate2.pem")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-root-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -%% @doc once rootCA cert present in cacertfile, sibling CA signed Client cert could connect. -t_conn_success_with_server_all_CA_bundle_and_client_root_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "all-CAcerts-bundle.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - {ok, Socket} = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-root-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_conn_fail_with_server_two_IA_bundle_and_client_root_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "two-intermediates-bundle.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2-root-bundle.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_conn_fail_with_server_partial_chain_false_intermediate_cacert_and_client_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "intermediate1.pem")}, - {certfile, filename:join(DataDir, "server1.pem")}, - {keyfile, filename:join(DataDir, "server1.key")}, - {partial_chain, false} - ]} - ], - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options), - Res = ssl:connect( - {127, 0, 0, 1}, - Port, - [ - {keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - | client_default_tls_opts() - ], - 1000 - ), - fail_when_no_ssl_alert(Res, unknown_ca). - -t_error_handling_invalid_cacertfile(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% trigger error - Options = [ - {ssl_options, - ?config(ssl_config, Config) ++ - [ - {cacertfile, filename:join(DataDir, "server2.key")}, - {certfile, filename:join(DataDir, "server2.pem")}, - {keyfile, filename:join(DataDir, "server2.key")} - ]} - ], - ?assertException( - throw, - {error, rootfun_trusted_ca_from_cacertfile}, - emqx_start_listener(?FUNCTION_NAME, ssl, Port, Options) - ). - -ssl_config_verify_partial_chain() -> - [ - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {partial_chain, true} - ]. - -client_default_tls_opts() -> - [ - {versions, ['tlsv1.2']}, - {verify, verify_none} - ]. diff --git a/apps/emqx/test/emqx_test_tls_certs_helper.erl b/apps/emqx/test/emqx_test_tls_certs_helper.erl deleted file mode 100644 index 78d51c5e0..000000000 --- a/apps/emqx/test/emqx_test_tls_certs_helper.erl +++ /dev/null @@ -1,319 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_test_tls_certs_helper). --export([ - gen_ca/2, - gen_host_cert/3, - gen_host_cert/4, - - select_free_port/1, - generate_tls_certs/1, - - fail_when_ssl_error/1, - fail_when_ssl_error/2, - fail_when_no_ssl_alert/2, - fail_when_no_ssl_alert/3, - - emqx_start_listener/4 -]). - --include_lib("common_test/include/ct.hrl"). - -%%------------------------------------------------------------------------------- -%% Start Listener -%%------------------------------------------------------------------------------- -emqx_start_listener(Name, Type, Port, Opts) when is_list(Opts) -> - emqx_start_listener(Name, Type, Port, maps:from_list(Opts)); -emqx_start_listener(Name, ssl, Port, #{ssl_options := SslOptions} = Opts0) -> - Opts = Opts0#{ - enable => true, - bind => {{127, 0, 0, 1}, Port}, - mountpoint => <<>>, - zone => default, - ssl_options => maps:from_list(SslOptions) - }, - ct:pal("start listener with ~p ~p", [Name, Opts]), - emqx_listeners:start_listener(ssl, Name, Opts). - -%%------------------------------------------------------------------------------- -%% TLS certs -%%------------------------------------------------------------------------------- -gen_ca(Path, Name) -> - %% Generate ca.pem and ca.key which will be used to generate certs - %% for hosts server and clients - ECKeyFile = eckey_name(Path), - filelib:ensure_dir(ECKeyFile), - os:cmd("openssl ecparam -name secp256r1 > " ++ ECKeyFile), - Cmd = lists:flatten( - io_lib:format( - "openssl req -new -x509 -nodes " - "-newkey ec:~s " - "-keyout ~s -out ~s -days 3650 " - "-addext basicConstraints=CA:TRUE " - "-subj \"/C=SE/O=TEST CA\"", - [ - ECKeyFile, - ca_key_name(Path, Name), - ca_cert_name(Path, Name) - ] - ) - ), - os:cmd(Cmd). - -ca_cert_name(Path, Name) -> - filename(Path, "~s.pem", [Name]). -ca_key_name(Path, Name) -> - filename(Path, "~s.key", [Name]). - -eckey_name(Path) -> - filename(Path, "ec.key", []). - -gen_host_cert(H, CaName, Path) -> - gen_host_cert(H, CaName, Path, #{}). - -gen_host_cert(H, CaName, Path, Opts) -> - ECKeyFile = eckey_name(Path), - CN = str(H), - HKey = filename(Path, "~s.key", [H]), - HCSR = filename(Path, "~s.csr", [H]), - HCSR2 = filename(Path, "~s.csr", [H]), - HPEM = filename(Path, "~s.pem", [H]), - HPEM2 = filename(Path, "~s_renewed.pem", [H]), - HEXT = filename(Path, "~s.extfile", [H]), - PasswordArg = - case maps:get(password, Opts, undefined) of - undefined -> - " -nodes "; - Password -> - io_lib:format(" -passout pass:'~s' ", [Password]) - end, - - create_file( - HEXT, - "keyUsage=digitalSignature,keyAgreement,keyCertSign\n" - "basicConstraints=CA:TRUE \n" - "~s \n" - "subjectAltName=DNS:~s\n", - [maps:get(ext, Opts, ""), CN] - ), - - CSR_Cmd = csr_cmd(PasswordArg, ECKeyFile, HKey, HCSR, CN), - CSR_Cmd2 = csr_cmd(PasswordArg, ECKeyFile, HKey, HCSR2, CN), - - CERT_Cmd = cert_sign_cmd( - HEXT, HCSR, ca_cert_name(Path, CaName), ca_key_name(Path, CaName), HPEM - ), - %% 2nd cert for testing renewed cert. - CERT_Cmd2 = cert_sign_cmd( - HEXT, HCSR2, ca_cert_name(Path, CaName), ca_key_name(Path, CaName), HPEM2 - ), - ct:pal(os:cmd(CSR_Cmd)), - ct:pal(os:cmd(CSR_Cmd2)), - ct:pal(os:cmd(CERT_Cmd)), - ct:pal(os:cmd(CERT_Cmd2)), - file:delete(HEXT). - -cert_sign_cmd(ExtFile, CSRFile, CACert, CAKey, OutputCert) -> - lists:flatten( - io_lib:format( - "openssl x509 -req " - "-extfile ~s " - "-in ~s -CA ~s -CAkey ~s -CAcreateserial " - "-out ~s -days 500", - [ - ExtFile, - CSRFile, - CACert, - CAKey, - OutputCert - ] - ) - ). - -csr_cmd(PasswordArg, ECKeyFile, HKey, HCSR, CN) -> - lists:flatten( - io_lib:format( - "openssl req -new ~s -newkey ec:~s " - "-keyout ~s -out ~s " - "-addext \"subjectAltName=DNS:~s\" " - "-addext basicConstraints=CA:TRUE " - "-addext keyUsage=digitalSignature,keyAgreement,keyCertSign " - "-subj \"/C=SE/O=TEST/CN=~s\"", - [PasswordArg, ECKeyFile, HKey, HCSR, CN, CN] - ) - ). - -filename(Path, F, A) -> - filename:join(Path, str(io_lib:format(F, A))). - -str(Arg) -> - binary_to_list(iolist_to_binary(Arg)). - -create_file(Filename, Fmt, Args) -> - filelib:ensure_dir(Filename), - {ok, F} = file:open(Filename, [write]), - try - io:format(F, Fmt, Args) - after - file:close(F) - end, - ok. - -%% @doc get unused port from OS --spec select_free_port(tcp | udp | ssl | quic) -> inets:port_number(). -select_free_port(tcp) -> - select_free_port(gen_tcp, listen); -select_free_port(udp) -> - select_free_port(gen_udp, open); -select_free_port(ssl) -> - select_free_port(tcp); -select_free_port(quic) -> - select_free_port(udp). - -select_free_port(GenModule, Fun) when - GenModule == gen_tcp orelse - GenModule == gen_udp --> - {ok, S} = GenModule:Fun(0, [{reuseaddr, true}]), - {ok, Port} = inet:port(S), - ok = GenModule:close(S), - case os:type() of - {unix, darwin} -> - %% in MacOS, still get address_in_use after close port - timer:sleep(500); - _ -> - skip - end, - ct:pal("Select free OS port: ~p", [Port]), - Port. - -%% @doc fail the test if ssl_error recvd -%% post check for success conn establishment -fail_when_ssl_error(Socket) -> - fail_when_ssl_error(Socket, 1000). -fail_when_ssl_error(Socket, Timeout) -> - receive - {ssl_error, Socket, _} -> - ct:fail("Handshake failed!") - after Timeout -> - ok - end. - -%% @doc fail the test if no ssl_error -fail_when_no_ssl_alert(Res, Alert) -> - fail_when_no_ssl_alert(Res, Alert, 1000). - -fail_when_no_ssl_alert({error, {tls_alert, {Alert, _}}}, Alert, _Timeout) -> - ok; -fail_when_no_ssl_alert({error, _} = Other, Alert, _Timeout) -> - ct:fail("returned unexpected ssl_error: ~p, expected ~n", [Other, Alert]); -fail_when_no_ssl_alert({ok, Socket}, Alert, Timeout) -> - fail_when_no_ssl_alert(Socket, Alert, Timeout); -fail_when_no_ssl_alert(Socket, Alert, Timeout) -> - receive - {ssl_error, Socket, {tls_alert, {Alert, AlertInfo}}} -> - ct:pal("alert info: ~p~n", [AlertInfo]); - {ssl_error, Socket, Other} -> - ct:fail("recv unexpected ssl_error: ~p~n", [Other]) - after Timeout -> - ct:fail("No expected alert: ~p from Socket: ~p ", [Alert, Socket]) - end. - -%% @doc Generate TLS cert chain for tests -generate_tls_certs(Config) -> - DataDir = ?config(data_dir, Config), - gen_ca(DataDir, "root"), - gen_host_cert("intermediate1", "root", DataDir), - gen_host_cert("intermediate2", "root", DataDir), - gen_host_cert("server1", "intermediate1", DataDir), - gen_host_cert("client1", "intermediate1", DataDir), - gen_host_cert("server2", "intermediate2", DataDir), - gen_host_cert("client2", "intermediate2", DataDir), - - %% Build bundles below - os:cmd( - io_lib:format("cat ~p ~p ~p > ~p", [ - filename:join(DataDir, "client2.pem"), - filename:join(DataDir, "intermediate2.pem"), - filename:join(DataDir, "root.pem"), - filename:join(DataDir, "client2-complete-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "client2.pem"), - filename:join(DataDir, "intermediate2.pem"), - filename:join(DataDir, "client2-intermediate2-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "client2.pem"), - filename:join(DataDir, "root.pem"), - filename:join(DataDir, "client2-root-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "server1.pem"), - filename:join(DataDir, "intermediate1.pem"), - filename:join(DataDir, "server1-intermediate1-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "intermediate1.pem"), - filename:join(DataDir, "server1.pem"), - filename:join(DataDir, "intermediate1-server1-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "intermediate1_renewed.pem"), - filename:join(DataDir, "root.pem"), - filename:join(DataDir, "intermediate1_renewed-root-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "intermediate2.pem"), - filename:join(DataDir, "intermediate2_renewed.pem"), - filename:join(DataDir, "intermediate2_renewed_old-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "intermediate1.pem"), - filename:join(DataDir, "root.pem"), - filename:join(DataDir, "intermediate1-root-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p ~p > ~p", [ - filename:join(DataDir, "root.pem"), - filename:join(DataDir, "intermediate2.pem"), - filename:join(DataDir, "intermediate1.pem"), - filename:join(DataDir, "all-CAcerts-bundle.pem") - ]) - ), - os:cmd( - io_lib:format("cat ~p ~p > ~p", [ - filename:join(DataDir, "intermediate2.pem"), - filename:join(DataDir, "intermediate1.pem"), - filename:join(DataDir, "two-intermediates-bundle.pem") - ]) - ). diff --git a/apps/emqx_gateway/src/emqx_gateway_utils.erl b/apps/emqx_gateway/src/emqx_gateway_utils.erl index 3150ec675..8fd9a1519 100644 --- a/apps/emqx_gateway/src/emqx_gateway_utils.erl +++ b/apps/emqx_gateway/src/emqx_gateway_utils.erl @@ -559,8 +559,6 @@ ssl_opts(Name, Opts) -> [ fun ssl_opts_crl_config/2, fun ssl_opts_drop_unsupported/2, - fun ssl_partial_chain/2, - fun ssl_verify_fun/2, fun ssl_server_opts/2 ], SSLOpts, @@ -588,12 +586,6 @@ ssl_server_opts(SSLOpts, ssl_options) -> ssl_server_opts(SSLOpts, dtls_options) -> emqx_tls_lib:to_server_opts(dtls, SSLOpts). -ssl_partial_chain(SSLOpts, _Options) -> - emqx_tls_lib:opt_partial_chain(SSLOpts). - -ssl_verify_fun(SSLOpts, _Options) -> - emqx_tls_lib:opt_verify_fun(SSLOpts). - ranch_opts(Type, ListenOn, Opts) -> NumAcceptors = maps:get(acceptors, Opts, 4), MaxConnections = maps:get(max_connections, Opts, 1024), diff --git a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl index 496192e39..a2d4d21af 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl @@ -421,7 +421,6 @@ t_create_webhook_v1_bridges_api(Config) -> <<"enable">> => true, <<"hibernate_after">> => <<"5s">>, <<"log_level">> => <<"notice">>, - <<"partial_chain">> => false, <<"reuse_sessions">> => true, <<"secure_renegotiate">> => true, <<"user_lookup_fun">> => diff --git a/changes/ce/feat-11721.en.md b/changes/ce/feat-11721.en.md deleted file mode 100644 index 37eac8a5f..000000000 --- a/changes/ce/feat-11721.en.md +++ /dev/null @@ -1,22 +0,0 @@ -Enhance TLS listener to support more flexible TLS verifications. - -- partial_chain support - - If the option `partial_chain` is set to `true`, allow connections with incomplete certificate chains. - - Check the configuration manual document for more details. - -- Certificate KeyUsage Validation - - Added support for required Extended Key Usage defined in - [rfc5280](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12). - - Introduced a new option (`verify_peer_ext_key_usage`) to require specific key usages (like "serverAuth") - in peer certificates during the TLS handshake. - This strengthens security by ensuring certificates are used for their intended purposes. - - example: - "serverAuth,OID:1.3.6.1.5.5.7.3.2" - - Check the configuration manual document for more details. - diff --git a/mix.exs b/mix.exs index b31164e65..e81617dbb 100644 --- a/mix.exs +++ b/mix.exs @@ -101,8 +101,7 @@ defmodule EMQXUmbrella.MixProject do {:bcrypt, github: "emqx/erlang-bcrypt", tag: "0.6.2", override: true}, {:uuid, github: "okeuday/uuid", tag: "v2.0.6", override: true}, {:quickrand, github: "okeuday/quickrand", tag: "v2.0.6", override: true}, - {:ra, "2.7.3", override: true}, - {:mimerl, "1.2.0", override: true} + {:ra, "2.7.3", override: true} ] ++ emqx_apps(profile_info, version) ++ enterprise_deps(profile_info) ++ jq_dep() ++ quicer_dep() diff --git a/rebar.config b/rebar.config index be47f8e4d..51a7ed17c 100644 --- a/rebar.config +++ b/rebar.config @@ -111,8 +111,7 @@ {ssl_verify_fun, "1.1.7"}, {rfc3339, {git, "https://github.com/emqx/rfc3339.git", {tag, "0.2.3"}}}, {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {tag, "0.6.2"}}}, - {ra, "2.7.3"}, - {mimerl, "1.2.0"} + {ra, "2.7.3"} ]}. {xref_ignores, diff --git a/rel/i18n/emqx_schema.hocon b/rel/i18n/emqx_schema.hocon index c6ec68d63..e80f36817 100644 --- a/rel/i18n/emqx_schema.hocon +++ b/rel/i18n/emqx_schema.hocon @@ -684,49 +684,6 @@ common_ssl_opts_schema_verify.desc: common_ssl_opts_schema_verify.label: """Verify peer""" -common_ssl_opts_schema_partial_chain.desc: -"""Enable or disable peer verification with partial_chain. -When local verifies a peer certificate during the x509 path validation -process, it constructs a certificate chain that starts with the peer -certificate and ends with a trust anchor. -By default, if it is set to `false`, the trust anchor is the -Root CA, and the certificate chain must be complete. -However, if the setting is set to `true` or `cacert_from_cacertfile`, -the last certificate in `cacertfile` will be used as the trust anchor -certificate (intermediate CA). This creates a partial chain -in the path validation. -Alternatively, if it is configured with `two_cacerts_from_cacertfile`, -one of the last two certificates in `cacertfile` will be used as the -trust anchor certificate, forming a partial chain. This option is -particularly useful for intermediate CA certificate rotation. -However, please note that it incurs some additional overhead, so it -should only be used for certificate rotation purposes.""" - -common_ssl_opts_schema_partial_chain.label: -"""Partial chain""" - -common_ssl_opts_verify_peer_ext_key_usage.desc: -"""Verify extended key usage in peer's certificate -For additional peer certificate validation, the value defined here must present in the -'Extended Key Usage' of peer certificate defined in -[rfc5280](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12). - -Allowed values are -- `clientAuth` -- `serverAuth` -- `codeSigning` -- `emailProtection` -- `timeStamping` -- `ocspSigning` -- raw OID, for example: "OID:1.3.6.1.5.5.7.3.2" means `id-pk 2` which is equivalent to `clientAuth` - -Comma-separated string is also supported for validating more than one key usages. - -For example, `"serverAuth,OID:1.3.6.1.5.5.7.3.2"`""" - -common_ssl_opts_verify_peer_ext_key_usage.label: -"""Verify KeyUsage in cert""" - fields_listeners_ssl.desc: """SSL listeners.""" diff --git a/scripts/spellcheck/dicts/emqx.txt b/scripts/spellcheck/dicts/emqx.txt index ce08d0f6b..7c888af49 100644 --- a/scripts/spellcheck/dicts/emqx.txt +++ b/scripts/spellcheck/dicts/emqx.txt @@ -310,4 +310,3 @@ ElasticSearch doc_as_upsert upsert aliyun -OID