test: port listener tls partial_chain
This commit is contained in:
parent
0b95a08d32
commit
fa4357ce89
|
@ -0,0 +1,248 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% 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("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")}
|
||||
],
|
||||
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")}
|
||||
],
|
||||
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")}
|
||||
],
|
||||
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")}
|
||||
],
|
||||
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")}
|
||||
],
|
||||
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")}
|
||||
],
|
||||
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),
|
||||
{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_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")}
|
||||
],
|
||||
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}
|
||||
].
|
|
@ -0,0 +1,360 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% 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_keyusage_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,
|
||||
gen_host_cert/4
|
||||
]).
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, full_chain},
|
||||
{group, partial_chain}
|
||||
].
|
||||
|
||||
all_tc() ->
|
||||
emqx_ct: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_listeners:start_listener(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")}
|
||||
],
|
||||
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_listeners:start_listener(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")}
|
||||
],
|
||||
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_listeners:start_listener(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)}
|
||||
],
|
||||
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_listeners:start_listener(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)}
|
||||
],
|
||||
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_listeners:start_listener(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)}
|
||||
],
|
||||
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_listeners:start_listener(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)}
|
||||
],
|
||||
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_listeners:start_listener(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)}
|
||||
],
|
||||
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_listeners:start_listener(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")}
|
||||
],
|
||||
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_listeners:start_listener(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)}
|
||||
],
|
||||
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_listeners:start_listener(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")}
|
||||
],
|
||||
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}
|
||||
].
|
|
@ -0,0 +1,668 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% 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("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),
|
||||
dbg:tracer(process, {fun dbg:dhandler/2, group_leader()}),
|
||||
dbg:p(all, c),
|
||||
dbg:tpl(emqx_listeners, esockd_opts, cx),
|
||||
dbg:tpl(emqx_listeners, inject_root_fun, cx),
|
||||
dbg:tpl(esockd, open, cx),
|
||||
|
||||
[{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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate1_renewed.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")}
|
||||
],
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate2_renewed.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")}
|
||||
],
|
||||
1000
|
||||
),
|
||||
fail_when_no_ssl_alert(Socket, unknown_ca),
|
||||
ssl:close(Socket).
|
||||
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate2_renewed.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-intermediate2-bundle.pem")}
|
||||
],
|
||||
1000
|
||||
),
|
||||
fail_when_no_ssl_alert(Socket, unknown_ca),
|
||||
ssl:close(Socket).
|
||||
|
||||
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, [
|
||||
{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}
|
||||
| ?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_renewed.pem")}
|
||||
],
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate2_renewed.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_renewed.pem")}
|
||||
],
|
||||
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, [
|
||||
{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}
|
||||
| ?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")}
|
||||
],
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate2_renewed_old-bundle.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-intermediate2-bundle.pem")}
|
||||
],
|
||||
1000
|
||||
),
|
||||
fail_when_no_ssl_alert(Socket, unknown_ca),
|
||||
ssl:close(Socket).
|
||||
|
||||
%% @doc verify when config (two_cacerts_from_cacertfile) allows two versions of certs from same trusted CA.
|
||||
t_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, [
|
||||
{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}
|
||||
| ?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-intermediate2-bundle.pem")}
|
||||
],
|
||||
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_fail_success_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, [
|
||||
{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}
|
||||
| ?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, "all-CAcerts-bundle.pem")}
|
||||
],
|
||||
1000
|
||||
),
|
||||
fail_when_no_ssl_alert(Socket, unknown_ca),
|
||||
ssl:close(Socket).
|
||||
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate1_renewed.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")}
|
||||
],
|
||||
1000
|
||||
),
|
||||
fail_when_no_ssl_alert(Socket, unknown_ca),
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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_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")}
|
||||
],
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "all-CAcerts-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, "client2.key")},
|
||||
{certfile, filename:join(DataDir, "client2-root-bundle.pem")}
|
||||
],
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "two-intermediates-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, "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_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, [
|
||||
{cacertfile, filename:join(DataDir, "intermediate1.pem")},
|
||||
{certfile, filename:join(DataDir, "server1.pem")},
|
||||
{keyfile, filename:join(DataDir, "server1.key")},
|
||||
{partial_chain, false}
|
||||
| ?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")}
|
||||
],
|
||||
1000
|
||||
),
|
||||
fail_when_no_ssl_alert(Socket, unknown_ca),
|
||||
ssl:close(Socket).
|
||||
|
||||
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, [
|
||||
{cacertfile, filename:join(DataDir, "server2.key")},
|
||||
{certfile, filename:join(DataDir, "server2.pem")},
|
||||
{keyfile, filename:join(DataDir, "server2.key")}
|
||||
| ?config(ssl_config, Config)
|
||||
]}
|
||||
],
|
||||
?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}
|
||||
].
|
|
@ -0,0 +1,311 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% 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_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#{
|
||||
bind => {{127, 0, 0, 1}, Port},
|
||||
mountpoint => <<>>,
|
||||
zone => default,
|
||||
ssl_options => maps:from_list(SslOptions)
|
||||
},
|
||||
ct:pal("start listsner 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 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),
|
||||
|
||||
%% 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")
|
||||
])
|
||||
).
|
Loading…
Reference in New Issue