%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2019-2019. 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.
%%
%% %CopyrightEnd%
%%
%%
-module(ssl_cert_tests).
%% Note: This directive should only be used in test suites.
-compile(export_all).
-include_lib("public_key/include/public_key.hrl").
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
no_auth() ->
[{doc,"Test connection without authentication"}].
no_auth(Config) ->
ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
auth() ->
[{doc,"Test connection with mutual authentication"}].
auth(Config) ->
ClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
client_auth_empty_cert_accepted() ->
[{doc,"Test client authentication when client sends an empty certificate and "
"fail_if_no_peer_cert is set to false."}].
client_auth_empty_cert_accepted(Config) ->
ClientOpts = proplists:delete(keyfile,
proplists:delete(certfile,
ssl_test_lib:ssl_options(client_cert_opts, Config))),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
ServerOpts = [{verify, verify_peer},
{fail_if_no_peer_cert, false} | ServerOpts0],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
client_auth_empty_cert_rejected() ->
[{doc,"Test client authentication when client sends an empty certificate and "
"fail_if_no_peer_cert is set to true."}].
client_auth_empty_cert_rejected(Config) ->
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
| ssl_test_lib:ssl_options(server_cert_opts, Config)],
ClientOpts0 = ssl_test_lib:ssl_options([], Config),
%% Delete Client Cert and Key
ClientOpts1 = proplists:delete(certfile, ClientOpts0),
ClientOpts = proplists:delete(keyfile, ClientOpts1),
Version = proplists:get_value(version,Config),
case Version of
'tlsv1.3' ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required);
_ ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure)
end.
%%--------------------------------------------------------------------
client_auth_partial_chain() ->
[{doc, "Client sends an incompleate chain, by default not acceptable."}].
client_auth_partial_chain(Config) when is_list(Config) ->
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
| ssl_test_lib:ssl_options(server_cert_opts, Config)],
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
{ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts0)),
[{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs),
ClientOpts = [{cacerts, [RootCA]} |
proplists:delete(cacertfile, ClientOpts0)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
client_auth_allow_partial_chain() ->
[{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}].
client_auth_allow_partial_chain(Config) when is_list(Config) ->
ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
| ssl_test_lib:ssl_options(server_cert_opts, Config)],
ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
{ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)),
[{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs),
PartialChain = fun(CertChain) ->
case lists:member(IntermidiateCA, CertChain) of
true ->
{trusted_ca, IntermidiateCA};
false ->
unknown_ca
end
end,
ServerOpts = [{cacerts, [IntermidiateCA]},
{partial_chain, PartialChain} |
proplists:delete(cacertfile, ServerOpts0)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
client_auth_do_not_allow_partial_chain() ->
[{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, "
"and we do not choose to trust the intermediate CA. (partial_chain option)"}].
client_auth_do_not_allow_partial_chain(Config) when is_list(Config) ->
ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
| ssl_test_lib:ssl_options(server_cert_opts, Config)],
ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
{ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)),
[{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs),
PartialChain = fun(_CertChain) ->
unknown_ca
end,
ServerOpts = [{cacerts, [IntermidiateCA]},
{partial_chain, PartialChain} |
proplists:delete(cacertfile, ServerOpts0)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
client_auth_partial_chain_fun_fail() ->
[{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}].
client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
| ssl_test_lib:ssl_options(server_cert_opts, Config)],
ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
{ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)),
[{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs),
PartialChain = fun(_CertChain) ->
true = false %% crash on purpose
end,
ServerOpts = [{cacerts, [IntermidiateCA]},
{partial_chain, PartialChain} |
proplists:delete(cacertfile, ServerOpts0)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
[{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}].
missing_root_cert_no_auth(Config) ->
ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
invalid_signature_client() ->
[{doc,"Test server with invalid signature"}].
invalid_signature_client(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
PrivDir = proplists:get_value(priv_dir, Config),
KeyFile = proplists:get_value(keyfile, ClientOpts0),
[KeyEntry] = ssl_test_lib:pem_to_der(KeyFile),
Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)),
ClientCertFile = proplists:get_value(certfile, ClientOpts0),
NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"),
[{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile),
ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp),
ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate,
NewClientDerCert = public_key:pkix_sign(ClientOTPTbsCert, Key),
ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]),
ClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)],
ServerOpts = [{verify, verify_peer} | ServerOpts0],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
invalid_signature_server() ->
[{doc,"Test client with invalid signature"}].
invalid_signature_server(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
PrivDir = proplists:get_value(priv_dir, Config),
KeyFile = proplists:get_value(keyfile, ServerOpts0),
[KeyEntry] = ssl_test_lib:pem_to_der(KeyFile),
Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)),
ServerCertFile = proplists:get_value(certfile, ServerOpts0),
NewServerCertFile = filename:join(PrivDir, "server_invalid_cert.pem"),
[{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile),
ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp),
ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate,
NewServerDerCert = public_key:pkix_sign(ServerOTPTbsCert, Key),
ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]),
ServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts0)],
ClientOpts = [{verify, verify_peer} | ClientOpts0],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
%% TLS 1.3 Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
hello_retry_request() ->
[{doc,"Test that ssl server can request a new group when the client's first key share"
"is not supported"}].
hello_retry_request(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
{ServerOpts, ClientOpts} = group_config(Config,
[{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0],
[{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
custom_groups() ->
[{doc,"Test that ssl server can select a common group for key-exchange"}].
custom_groups(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
{ServerOpts, ClientOpts} = group_config_custom(Config,
[{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0],
[{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
%% Triggers a Server Alert as ssl client does not have a certificate with a
%% signature algorithm supported by the server (signature_algorithms_cert extension
%% of CertificateRequest does not contain the algorithm of the client certificate).
%% ssl client sends an empty certificate.
unsupported_sign_algo_cert_client_auth() ->
[{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}].
unsupported_sign_algo_cert_client_auth(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
{signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]},
%% Skip rsa_pkcs1_sha256!
{signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]},
{fail_if_no_peer_cert, true}|ServerOpts0],
ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required).
%%--------------------------------------------------------------------
unsupported_sign_algo_client_auth() ->
[{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}].
unsupported_sign_algo_client_auth(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
%% Skip rsa_pkcs1_sha256!
{signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]},
{fail_if_no_peer_cert, true}|ServerOpts0],
ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security).
%%--------------------------------------------------------------------
hello_retry_client_auth() ->
[{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}].
hello_retry_client_auth(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
{ServerOpts, ClientOpts} = group_config(Config,
[{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
{fail_if_no_peer_cert, true} | ServerOpts0],
[{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
hello_retry_client_auth_empty_cert_accepted() ->
[{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty "
"certificate and fail_if_no_peer_cert is set to false."}].
hello_retry_client_auth_empty_cert_accepted(Config) ->
ClientOpts0 = proplists:delete(keyfile,
proplists:delete(certfile,
ssl_test_lib:ssl_options(client_cert_opts, Config))),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
{ServerOpts, ClientOpts} = group_config(Config,
[{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
{fail_if_no_peer_cert, false} | ServerOpts0],
[{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
hello_retry_client_auth_empty_cert_rejected() ->
[{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client "
"sends an empty certificate and fail_if_no_peer_cert is set to true."}].
hello_retry_client_auth_empty_cert_rejected(Config) ->
ClientOpts0 = proplists:delete(keyfile,
proplists:delete(certfile,
ssl_test_lib:ssl_options(client_cert_opts, Config))),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
{ServerOpts, ClientOpts} = group_config(Config,
[{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
{fail_if_no_peer_cert, true} | ServerOpts0],
[{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]),
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required).
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
group_config_custom(Config, ServerOpts, ClientOpts) ->
case proplists:get_value(client_type, Config) of
erlang ->
{[{groups,"X448:P-256:P-384"} | ServerOpts],
[{supported_groups, [secp384r1, secp256r1, x25519]} | ClientOpts]};
openssl ->
{[{supported_groups, [x448, secp256r1, secp384r1]} | ServerOpts],
[{groups,"P-384:P-256:X25519"} | ClientOpts]}
end.
group_config(Config, ServerOpts, ClientOpts) ->
case proplists:get_value(client_type, Config) of
erlang ->
{[{groups,"X448:X25519"} | ServerOpts],
[{supported_groups, [secp256r1, x25519]} | ClientOpts]};
openssl ->
{[{supported_groups, [x448, x25519]} | ServerOpts],
[{groups,"P-256:X25519"} | ClientOpts]}
end.
test_ciphers(_, 'tlsv1.3' = Version) ->
Ciphers = ssl:cipher_suites(default, Version),
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
OpenSSLCiphers = openssl_ciphers(),
ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
lists:filter(fun(C) ->
ct:log("Cipher ~p~n", [C]),
lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers)
end, Ciphers);
test_ciphers(Kex, Version) ->
Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version),
[{key_exchange, Kex}]),
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
OpenSSLCiphers = openssl_ciphers(),
ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
lists:filter(fun(C) ->
ct:log("Cipher ~p~n", [C]),
lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers)
end, Ciphers).
openssl_ciphers() ->
Str = os:cmd("openssl ciphers"),
string:split(string:strip(Str, right, $\n), ":", all).