Merge pull request #10077 from qzhuyan/dev/william/quic-cert-password
feat(quic): support TLS password protected keyfile
This commit is contained in:
commit
65ef9c9086
|
@ -388,7 +388,11 @@ do_start_listener(quic, ListenerName, #{bind := Bind} = Opts) ->
|
||||||
] ++
|
] ++
|
||||||
case maps:get(cacertfile, SSLOpts, undefined) of
|
case maps:get(cacertfile, SSLOpts, undefined) of
|
||||||
undefined -> [];
|
undefined -> [];
|
||||||
CaCertFile -> [{cacertfile, binary_to_list(CaCertFile)}]
|
CaCertFile -> [{cacertfile, str(CaCertFile)}]
|
||||||
|
end ++
|
||||||
|
case maps:get(password, SSLOpts, undefined) of
|
||||||
|
undefined -> [];
|
||||||
|
Password -> [{password, str(Password)}]
|
||||||
end ++
|
end ++
|
||||||
optional_quic_listener_opts(Opts),
|
optional_quic_listener_opts(Opts),
|
||||||
ConnectionOpts = #{
|
ConnectionOpts = #{
|
||||||
|
|
|
@ -3023,9 +3023,9 @@ is_quic_ssl_opts(Name) ->
|
||||||
"cacertfile",
|
"cacertfile",
|
||||||
"certfile",
|
"certfile",
|
||||||
"keyfile",
|
"keyfile",
|
||||||
"verify"
|
"verify",
|
||||||
|
"password"
|
||||||
%% Followings are planned
|
%% Followings are planned
|
||||||
%% , "password"
|
|
||||||
%% , "hibernate_after"
|
%% , "hibernate_after"
|
||||||
%% , "fail_if_no_peer_cert"
|
%% , "fail_if_no_peer_cert"
|
||||||
%% , "handshake_timeout"
|
%% , "handshake_timeout"
|
||||||
|
|
|
@ -85,6 +85,13 @@
|
||||||
reset_proxy/2
|
reset_proxy/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
%% TLS certs API
|
||||||
|
-export([
|
||||||
|
gen_ca/2,
|
||||||
|
gen_host_cert/3,
|
||||||
|
gen_host_cert/4
|
||||||
|
]).
|
||||||
|
|
||||||
-define(CERTS_PATH(CertName), filename:join(["etc", "certs", CertName])).
|
-define(CERTS_PATH(CertName), filename:join(["etc", "certs", CertName])).
|
||||||
|
|
||||||
-define(MQTT_SSL_CLIENT_CERTS, [
|
-define(MQTT_SSL_CLIENT_CERTS, [
|
||||||
|
@ -562,6 +569,7 @@ ensure_quic_listener(Name, UdpPort, ExtraSettings) ->
|
||||||
mountpoint => <<>>,
|
mountpoint => <<>>,
|
||||||
zone => default
|
zone => default
|
||||||
},
|
},
|
||||||
|
|
||||||
Conf2 = maps:merge(Conf, ExtraSettings),
|
Conf2 = maps:merge(Conf, ExtraSettings),
|
||||||
emqx_config:put([listeners, quic, Name], Conf2),
|
emqx_config:put([listeners, quic, Name], Conf2),
|
||||||
case emqx_listeners:start_listener(emqx_listeners:listener_id(quic, Name)) of
|
case emqx_listeners:start_listener(emqx_listeners:listener_id(quic, Name)) of
|
||||||
|
@ -1075,6 +1083,104 @@ latency_up_proxy(off, Name, ProxyHost, ProxyPort) ->
|
||||||
).
|
).
|
||||||
|
|
||||||
%%-------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------
|
||||||
|
%% 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 = filename(Path, "~s-ec.key", [Name]),
|
||||||
|
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 "
|
||||||
|
"-subj \"/C=SE/O=Internet Widgits Pty Ltd 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]).
|
||||||
|
|
||||||
|
gen_host_cert(H, CaName, Path) ->
|
||||||
|
gen_host_cert(H, CaName, Path, #{}).
|
||||||
|
|
||||||
|
gen_host_cert(H, CaName, Path, Opts) ->
|
||||||
|
ECKeyFile = filename(Path, "~s-ec.key", [CaName]),
|
||||||
|
CN = str(H),
|
||||||
|
HKey = filename(Path, "~s.key", [H]),
|
||||||
|
HCSR = filename(Path, "~s.csr", [H]),
|
||||||
|
HPEM = filename(Path, "~s.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,
|
||||||
|
CSR_Cmd =
|
||||||
|
lists:flatten(
|
||||||
|
io_lib:format(
|
||||||
|
"openssl req -new ~s -newkey ec:~s "
|
||||||
|
"-keyout ~s -out ~s "
|
||||||
|
"-addext \"subjectAltName=DNS:~s\" "
|
||||||
|
"-addext keyUsage=digitalSignature,keyAgreement "
|
||||||
|
"-subj \"/C=SE/O=Internet Widgits Pty Ltd/CN=~s\"",
|
||||||
|
[PasswordArg, ECKeyFile, HKey, HCSR, CN, CN]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
create_file(
|
||||||
|
HEXT,
|
||||||
|
"keyUsage=digitalSignature,keyAgreement\n"
|
||||||
|
"subjectAltName=DNS:~s\n",
|
||||||
|
[CN]
|
||||||
|
),
|
||||||
|
CERT_Cmd =
|
||||||
|
lists:flatten(
|
||||||
|
io_lib:format(
|
||||||
|
"openssl x509 -req "
|
||||||
|
"-extfile ~s "
|
||||||
|
"-in ~s -CA ~s -CAkey ~s -CAcreateserial "
|
||||||
|
"-out ~s -days 500",
|
||||||
|
[
|
||||||
|
HEXT,
|
||||||
|
HCSR,
|
||||||
|
ca_cert_name(Path, CaName),
|
||||||
|
ca_key_name(Path, CaName),
|
||||||
|
HPEM
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ct:pal(os:cmd(CSR_Cmd)),
|
||||||
|
ct:pal(os:cmd(CERT_Cmd)),
|
||||||
|
file:delete(HEXT).
|
||||||
|
|
||||||
|
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.
|
||||||
|
%%-------------------------------------------------------------------------------
|
||||||
%% Testcase teardown utilities
|
%% Testcase teardown utilities
|
||||||
%%-------------------------------------------------------------------------------
|
%%-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
|
|
||||||
-define(CERTS_PATH(CertName), filename:join(["../../lib/emqx/etc/certs/", CertName])).
|
-define(CERTS_PATH(CertName), filename:join(["../../lib/emqx/etc/certs/", CertName])).
|
||||||
|
|
||||||
|
-define(SERVER_KEY_PASSWORD, "sErve7r8Key$!").
|
||||||
|
|
||||||
all() -> emqx_common_test_helpers:all(?MODULE).
|
all() -> emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
@ -33,6 +35,7 @@ init_per_suite(Config) ->
|
||||||
application:ensure_all_started(esockd),
|
application:ensure_all_started(esockd),
|
||||||
application:ensure_all_started(quicer),
|
application:ensure_all_started(quicer),
|
||||||
application:ensure_all_started(cowboy),
|
application:ensure_all_started(cowboy),
|
||||||
|
generate_tls_certs(Config),
|
||||||
lists:foreach(fun set_app_env/1, NewConfig),
|
lists:foreach(fun set_app_env/1, NewConfig),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
@ -45,11 +48,6 @@ init_per_testcase(Case, Config) when
|
||||||
->
|
->
|
||||||
catch emqx_config_handler:stop(),
|
catch emqx_config_handler:stop(),
|
||||||
{ok, _} = emqx_config_handler:start_link(),
|
{ok, _} = emqx_config_handler:start_link(),
|
||||||
case emqx_config:get([listeners], undefined) of
|
|
||||||
undefined -> ok;
|
|
||||||
Listeners -> emqx_config:put([listeners], maps:remove(quic, Listeners))
|
|
||||||
end,
|
|
||||||
|
|
||||||
PrevListeners = emqx_config:get([listeners], #{}),
|
PrevListeners = emqx_config:get([listeners], #{}),
|
||||||
PureListeners = remove_default_limiter(PrevListeners),
|
PureListeners = remove_default_limiter(PrevListeners),
|
||||||
PureListeners2 = PureListeners#{
|
PureListeners2 = PureListeners#{
|
||||||
|
@ -185,6 +183,50 @@ t_wss_conn(_) ->
|
||||||
{ok, Socket} = ssl:connect({127, 0, 0, 1}, 9998, [{verify, verify_none}], 1000),
|
{ok, Socket} = ssl:connect({127, 0, 0, 1}, 9998, [{verify, verify_none}], 1000),
|
||||||
ok = ssl:close(Socket).
|
ok = ssl:close(Socket).
|
||||||
|
|
||||||
|
t_quic_conn(Config) ->
|
||||||
|
Port = 24568,
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
SSLOpts = #{
|
||||||
|
password => ?SERVER_KEY_PASSWORD,
|
||||||
|
certfile => filename:join(DataDir, "server-password.pem"),
|
||||||
|
cacertfile => filename:join(DataDir, "ca.pem"),
|
||||||
|
keyfile => filename:join(DataDir, "server-password.key")
|
||||||
|
},
|
||||||
|
emqx_common_test_helpers:ensure_quic_listener(?FUNCTION_NAME, Port, #{ssl_options => SSLOpts}),
|
||||||
|
ct:pal("~p", [emqx_listeners:list()]),
|
||||||
|
{ok, Conn} = quicer:connect(
|
||||||
|
{127, 0, 0, 1},
|
||||||
|
Port,
|
||||||
|
[
|
||||||
|
{verify, verify_none},
|
||||||
|
{alpn, ["mqtt"]}
|
||||||
|
],
|
||||||
|
1000
|
||||||
|
),
|
||||||
|
ok = quicer:close_connection(Conn),
|
||||||
|
emqx_listeners:stop_listener(quic, ?FUNCTION_NAME, #{bind => Port}).
|
||||||
|
|
||||||
|
t_ssl_password_cert(Config) ->
|
||||||
|
Port = 24568,
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
SSLOptsPWD = #{
|
||||||
|
password => ?SERVER_KEY_PASSWORD,
|
||||||
|
certfile => filename:join(DataDir, "server-password.pem"),
|
||||||
|
cacertfile => filename:join(DataDir, "ca.pem"),
|
||||||
|
keyfile => filename:join(DataDir, "server-password.key")
|
||||||
|
},
|
||||||
|
LConf = #{
|
||||||
|
enabled => true,
|
||||||
|
bind => {{127, 0, 0, 1}, Port},
|
||||||
|
mountpoint => <<>>,
|
||||||
|
zone => default,
|
||||||
|
ssl_options => SSLOptsPWD
|
||||||
|
},
|
||||||
|
ok = emqx_listeners:start_listener(ssl, ?FUNCTION_NAME, LConf),
|
||||||
|
{ok, SSLSocket} = ssl:connect("127.0.0.1", Port, [{verify, verify_none}]),
|
||||||
|
ssl:close(SSLSocket),
|
||||||
|
emqx_listeners:stop_listener(ssl, ?FUNCTION_NAME, LConf).
|
||||||
|
|
||||||
t_format_bind(_) ->
|
t_format_bind(_) ->
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
":1883",
|
":1883",
|
||||||
|
@ -269,3 +311,10 @@ remove_default_limiter(Listeners) ->
|
||||||
end,
|
end,
|
||||||
Listeners
|
Listeners
|
||||||
).
|
).
|
||||||
|
|
||||||
|
generate_tls_certs(Config) ->
|
||||||
|
DataDir = ?config(data_dir, Config),
|
||||||
|
emqx_common_test_helpers:gen_ca(DataDir, "ca"),
|
||||||
|
emqx_common_test_helpers:gen_host_cert("server-password", "ca", DataDir, #{
|
||||||
|
password => ?SERVER_KEY_PASSWORD
|
||||||
|
}).
|
||||||
|
|
|
@ -1569,7 +1569,7 @@ t_multi_streams_remote_shutdown(Config) ->
|
||||||
|
|
||||||
ok = stop_emqx(),
|
ok = stop_emqx(),
|
||||||
%% Client should be closed
|
%% Client should be closed
|
||||||
assert_client_die(C).
|
assert_client_die(C, 100, 50).
|
||||||
|
|
||||||
t_multi_streams_remote_shutdown_with_reconnect(Config) ->
|
t_multi_streams_remote_shutdown_with_reconnect(Config) ->
|
||||||
erlang:process_flag(trap_exit, true),
|
erlang:process_flag(trap_exit, true),
|
||||||
|
@ -2047,14 +2047,15 @@ via_stream({quic, _Conn, Stream}) ->
|
||||||
assert_client_die(C) ->
|
assert_client_die(C) ->
|
||||||
assert_client_die(C, 100, 10).
|
assert_client_die(C, 100, 10).
|
||||||
assert_client_die(C, _, 0) ->
|
assert_client_die(C, _, 0) ->
|
||||||
ct:fail("Client ~p did not die", [C]);
|
ct:fail("Client ~p did not die: stacktrace: ~p", [C, process_info(C, current_stacktrace)]);
|
||||||
assert_client_die(C, Delay, Retries) ->
|
assert_client_die(C, Delay, Retries) ->
|
||||||
case catch emqtt:info(C) of
|
try emqtt:info(C) of
|
||||||
{'EXIT', {noproc, {gen_statem, call, [_, info, infinity]}}} ->
|
Info when is_list(Info) ->
|
||||||
ok;
|
|
||||||
_Other ->
|
|
||||||
timer:sleep(Delay),
|
timer:sleep(Delay),
|
||||||
assert_client_die(C, Delay, Retries - 1)
|
assert_client_die(C, Delay, Retries - 1)
|
||||||
|
catch
|
||||||
|
exit:Error ->
|
||||||
|
ct:comment("client die with ~p", [Error])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% BUILD_WITHOUT_QUIC
|
%% BUILD_WITHOUT_QUIC
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add support for QUIC TLS password protected certificate file.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
增加对 QUIC TLS 密码保护证书文件的支持。
|
Loading…
Reference in New Issue