test(tls-partial-chains): renewed intermediate_cacert

This commit is contained in:
William Yang 2023-05-03 14:34:03 +02:00
parent 11c8e937b4
commit 8503d3c6dd
5 changed files with 94 additions and 34 deletions

View File

@ -8,8 +8,7 @@
Prior to the improvement, the `key` in `${key}` could only contain letters, numbers, and underscores. Now the `key` supports any UTF8 character after the improvement.
- Adds a new feature to enable partial certificate chain validation for TLS listeners[#10553](https://github.com/emqx/emqx/pull/10553).
If TLS listener has `partial_chain` set to `cacert_from_cacertfile`,
the certificate in the `cacertfile` will be used as the `cacert` for chain path validation. If the `cacertfile` has a chain of certificates, the cert at the end of the file will be used as the `cacert` for path validation.
If partial_chain is set to 'true', the last certificate in cacertfile is treated as the terminal of the certificate trust-chain. That is, the TLS handshake does not require full trust-chain, and EMQX will not try to validate the chain all the way up to the root CA.
## Bug fixes

View File

@ -8,8 +8,8 @@
改进前,`${key}` 中的 `key` 只能包含字母、数字和下划线。改进后 `key` 支持任意的 UTF8 字符了。
- 增加了一个新的功能为TLS监听器启用部分证书链验证[#10553](https://github.com/emqx/emqx/pull/10553)。
如果TLS监听器的 `partial_chain` 设置为 `cacert_from_cacertfile`,
`cacertfile` 中的证书将被用作链式路径验证的 `cacert` 。如果 `cacertfile` 文件有一连串的证书,文件末尾的证书将被用作路径验证的 `cacert`
如果 partial_chain 设置为“true”cacertfile 中的最后一个证书将被视为证书信任链的顶端证书。 也就是说TLS 握手不需要完整的链,并且 EMQX 不会尝试一直验证链直到根 CA。
## 修复

View File

@ -186,7 +186,9 @@ opt_partial_chain(SslOpts) ->
case proplists:get_value(partial_chain, SslOpts, undefined) of
undefined ->
SslOpts;
cacert_from_cacertfile ->
false ->
SslOpts;
V when V =:= cacert_from_cacertfile orelse V == true ->
replace(SslOpts, partial_chain, cacert_from_cacertfile(SslOpts))
end.

View File

@ -70,6 +70,51 @@ t_conn_success_with_intermediate_cacert_bundle(Config) ->
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_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_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_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),
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_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),
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),
@ -208,5 +253,5 @@ t_conn_success_with_server_intermediate_and_client_root_chain(Config) ->
ssl_config_verify_partial_chain() ->
[ {verify, verify_peer}
, {fail_if_no_peer_cert, true}
, {partial_chain, cacert_from_cacertfile}
, {partial_chain, true}
].

View File

@ -73,7 +73,9 @@ gen_host_cert(H, CaName, Path, Opts) ->
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
@ -82,44 +84,56 @@ gen_host_cert(H, CaName, Path, Opts) ->
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 basicConstraints=CA:TRUE "
"-addext keyUsage=digitalSignature,keyAgreement,keyCertSign "
"-subj \"/C=SE/O=Internet Widgits Pty Ltd/CN=~s\"",
[PasswordArg, ECKeyFile, HKey, HCSR, CN, CN]
)
),
create_file(
HEXT,
"keyUsage=digitalSignature,keyAgreement,keyCertSign\n"
"basicConstraints=CA:TRUE \n"
"subjectAltName=DNS:~s\n",
[CN]
HEXT,
"keyUsage=digitalSignature,keyAgreement,keyCertSign\n"
"basicConstraints=CA:TRUE \n"
"subjectAltName=DNS:~s\n",
[CN]
),
CERT_Cmd =
lists:flatten(
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, HCSR, 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",
[
HEXT,
HCSR,
ca_cert_name(Path, CaName),
ca_key_name(Path, CaName),
HPEM
ExtFile,
CSRFile,
CACert,
CAKey,
OutputCert
]
)
),
ct:pal(os:cmd(CSR_Cmd)),
ct:pal(os:cmd(CERT_Cmd)),
file:delete(HEXT).
).
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=Internet Widgits Pty Ltd/CN=~s\"",
[PasswordArg, ECKeyFile, HKey, HCSR, CN, CN]
)
).
filename(Path, F, A) ->
filename:join(Path, str(io_lib:format(F, A))).