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