From ea35b20035756380578f0eba8ff787ae2d1459e0 Mon Sep 17 00:00:00 2001 From: William Yang Date: Fri, 28 Apr 2023 17:10:35 +0200 Subject: [PATCH] test(tls): test refactoring --- test/emqx_listener_tls_verify_SUITE.erl | 339 ------------------ test/emqx_listener_tls_verify_chain_SUITE.erl | 157 ++++++++ ...istener_tls_verify_partial_chain_SUITE.erl | 212 +++++++++++ test/emqx_test_tls_certs_helper.erl | 71 ++++ 4 files changed, 440 insertions(+), 339 deletions(-) delete mode 100644 test/emqx_listener_tls_verify_SUITE.erl create mode 100644 test/emqx_listener_tls_verify_chain_SUITE.erl create mode 100644 test/emqx_listener_tls_verify_partial_chain_SUITE.erl diff --git a/test/emqx_listener_tls_verify_SUITE.erl b/test/emqx_listener_tls_verify_SUITE.erl deleted file mode 100644 index 49ba18e6c..000000000 --- a/test/emqx_listener_tls_verify_SUITE.erl +++ /dev/null @@ -1,339 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2023 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_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("emqx/include/emqx.hrl"). --include_lib("emqx/include/emqx_mqtt.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - --import(emqx_test_tls_certs_helper, [ gen_ca/2 - , gen_host_cert/3 - ]). - -all() -> emqx_ct:all(?MODULE). - -init_per_suite(Config) -> - generate_tls_certs(Config), - application:ensure_all_started(esockd), - Config. - -end_per_suite(_Config) -> - application:stop(esockd). - -t_tls_conn_success_when_partial_chain_enabled_with_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - ], 1000), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -t_tls_conn_success_when_partial_chain_enabled_with_intermediate_cacert_bundle(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "server1-intermediate1-bundle.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - ], 1000), - fail_when_ssl_error(Socket), - ssl:close(Socket). - -t_tls_conn_fail_when_partial_chain_enabled_with_intermediate_cacert_bundle2(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "intermediate1-server1-bundle.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ssl:close(Socket). - -t_tls_conn_fail_when_partial_chain_disabled_with_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - ], 1000), - - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - - -t_tls_conn_fail_when_partial_chain_disabled_with_other_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2.pem")} - ], 1000), - - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_tls_conn_fail_when_partial_chain_enabled_with_other_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client2.key")}, - {certfile, filename:join(DataDir, "client2.pem")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_tls_conn_success_when_partial_chain_enabled_root_ca_with_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "root.pem")} - , {certfile, filename:join(DataDir, "server2.pem")} - , {keyfile, filename:join(DataDir, "server2.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_tls_conn_success_when_partial_chain_disabled_with_intermediate_ca_cert(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(ssl, Port, Options), - {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, - {certfile, filename:join(DataDir, "client1.pem")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_tls_conn_success_when_partial_chain_disabled_with_broken_cert_chain_other_intermediate(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - %% Server has root ca cert - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "root.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - %% Client has complete chain - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_tls_conn_fail_when_partial_chain_enabled_with_other_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_tls_conn_success_when_partial_chain_enabled_with_complete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "intermediate2.pem")} - , {certfile, filename:join(DataDir, "server2.pem")} - , {keyfile, filename:join(DataDir, "server2.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - -t_tls_conn_success_when_partial_chain_disabled_with_complete_cert_chain_client(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "root.pem")} - , {certfile, filename:join(DataDir, "server2.pem")} - , {keyfile, filename:join(DataDir, "server2.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_ssl_error(Socket), - ok = ssl:close(Socket). - - -t_tls_conn_fail_when_partial_chain_disabled_with_incomplete_cert_chain_server(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "intermediate2.pem")} %% imcomplete at server side - , {certfile, filename:join(DataDir, "server2.pem")} - , {keyfile, filename:join(DataDir, "server2.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - - -t_tls_conn_fail_when_partial_chain_enabled_with_imcomplete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {partial_chain, cacert_from_cacertfile} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -t_tls_conn_fail_when_partial_chain_disabled_with_incomplete_cert_chain(Config) -> - Port = emqx_test_tls_certs_helper:select_free_port(ssl), - DataDir = ?config(data_dir, Config), - Options = [{ssl_options, [ {verify, verify_peer} - , {fail_if_no_peer_cert, true} - , {cacertfile, filename:join(DataDir, "intermediate1.pem")} - , {certfile, filename:join(DataDir, "server1.pem")} - , {keyfile, filename:join(DataDir, "server1.key")} - ]}], - emqx_listeners:start_listener(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")} - ], 1000), - fail_when_no_ssl_alert(Socket, unknown_ca), - ok = ssl:close(Socket). - -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), - 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, "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") - ])). - -fail_when_ssl_error(Socket) -> - receive - {ssl_error, Socket, _} -> - ct:fail("Handshake failed!") - after 1000 -> - ok - end. - -fail_when_no_ssl_alert(Socket, Alert) -> - 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 1000 -> - ct:fail("No expected alert: ~p from Socket: ~p ", [Alert, Socket]) - end. diff --git a/test/emqx_listener_tls_verify_chain_SUITE.erl b/test/emqx_listener_tls_verify_chain_SUITE.erl new file mode 100644 index 000000000..65f6a55b5 --- /dev/null +++ b/test/emqx_listener_tls_verify_chain_SUITE.erl @@ -0,0 +1,157 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 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("emqx/include/emqx.hrl"). +-include_lib("emqx/include/emqx_mqtt.hrl"). +-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 + ]). + + +all() -> emqx_ct: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_listeners:start_listener(ssl, Port, Options), + {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, + {certfile, filename:join(DataDir, "client1.pem")} + ], 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_listeners:start_listener(ssl, Port, Options), + {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client2.key")}, + {certfile, filename:join(DataDir, "client2.pem")} + ], 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_listeners:start_listener(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")} + ], 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 complete chain + emqx_listeners:start_listener(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")} + ], 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_listeners:start_listener(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")} + ], 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), + Options = [{ssl_options, [{cacertfile, filename:join(DataDir, "intermediate2.pem")} %% imcomplete at server side + , {certfile, filename:join(DataDir, "server2.pem")} + , {keyfile, filename:join(DataDir, "server2.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ok = ssl:close(Socket). + +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_listeners:start_listener(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")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ok = ssl:close(Socket). + +ssl_config_verify_peer() -> + [ {verify, verify_peer} + , {fail_if_no_peer_cert, true} + ]. diff --git a/test/emqx_listener_tls_verify_partial_chain_SUITE.erl b/test/emqx_listener_tls_verify_partial_chain_SUITE.erl new file mode 100644 index 000000000..e97f11cf8 --- /dev/null +++ b/test/emqx_listener_tls_verify_partial_chain_SUITE.erl @@ -0,0 +1,212 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 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("emqx/include/emqx.hrl"). +-include_lib("emqx/include/emqx_mqtt.hrl"). +-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 + ]). + + +all() -> emqx_ct: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, [ {cacertfile, filename:join(DataDir, "intermediate1.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(ssl, Port, Options), + {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, + {certfile, filename:join(DataDir, "client1.pem")} + ], 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, [ {cacertfile, filename:join(DataDir, "server1-intermediate1-bundle.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(ssl, Port, Options), + {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, + {certfile, filename:join(DataDir, "client1.pem")} + ], 1000), + fail_when_ssl_error(Socket), + ssl:close(Socket). + +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, [ {cacertfile, filename:join(DataDir, "intermediate1-server1-bundle.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(ssl, Port, Options), + {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client1.key")}, + {certfile, filename:join(DataDir, "client1.pem")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ssl:close(Socket). + +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, [ {cacertfile, filename:join(DataDir, "intermediate1.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(ssl, Port, Options), + {ok, Socket} = ssl:connect({127, 0, 0, 1}, Port, [{keyfile, filename:join(DataDir, "client2.key")}, + {certfile, filename:join(DataDir, "client2.pem")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ok = ssl:close(Socket). + +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, [ {cacertfile, filename:join(DataDir, "root.pem")} + , {certfile, filename:join(DataDir, "server2.pem")} + , {keyfile, filename:join(DataDir, "server2.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 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, [ {cacertfile, filename:join(DataDir, "intermediate1.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ok = ssl:close(Socket). + +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, [ {cacertfile, filename:join(DataDir, "intermediate1-root-bundle.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 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, [ {cacertfile, filename:join(DataDir, "intermediate2.pem")} + , {certfile, filename:join(DataDir, "server2.pem")} + , {keyfile, filename:join(DataDir, "server2.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 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, [ {cacertfile, filename:join(DataDir, "intermediate1.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ok = ssl:close(Socket). + +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, [ {cacertfile, filename:join(DataDir, "intermediate1.pem")} + , {certfile, filename:join(DataDir, "server1.pem")} + , {keyfile, filename:join(DataDir, "server1.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 1000), + fail_when_no_ssl_alert(Socket, unknown_ca), + ok = ssl:close(Socket). + +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, [ {cacertfile, filename:join(DataDir, "intermediate2.pem")} + , {certfile, filename:join(DataDir, "server2.pem")} + , {keyfile, filename:join(DataDir, "server2.key")} + | ?config(ssl_config, Config) + ]}], + emqx_listeners:start_listener(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")} + ], 1000), + fail_when_ssl_error(Socket), + ok = ssl:close(Socket). + +ssl_config_verify_partial_chain() -> + [ {verify, verify_peer} + , {fail_if_no_peer_cert, true} + , {partial_chain, cacert_from_cacertfile} + ]. diff --git a/test/emqx_test_tls_certs_helper.erl b/test/emqx_test_tls_certs_helper.erl index 36813074c..82b343fdb 100644 --- a/test/emqx_test_tls_certs_helper.erl +++ b/test/emqx_test_tls_certs_helper.erl @@ -20,8 +20,18 @@ , 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 + + ]). +-include_lib("common_test/include/ct.hrl"). + %%------------------------------------------------------------------------------- %% TLS certs %%------------------------------------------------------------------------------- @@ -154,3 +164,64 @@ select_free_port(GenModule, Fun) when 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 recvd +fail_when_no_ssl_alert(Socket, Alert) -> + fail_when_no_ssl_alert(Socket, Alert, 1000). +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), + 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.pem"), + filename:join(DataDir, "root.pem"), + filename:join(DataDir, "intermediate1-root-bundle.pem") + ])).