aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssl/test/ssl_cert_tests.erl
blob: c88daa2185693f2867989177cad57fadcb32bc3d (plain) (tree)




















































































































































































































































                                                                                                        


                                                                                                      















                                                                                             
                                                                                              
                                           
                                                                              

                                                                 














                                                                                        










                                                                                              
                                                                                         





                                                                                                                       
                                                                 



























                                                                                                                       
                                                                                         








                                                                                                                       









                                                                                    


                                                        
                                                       





                                                                         








                                                                                                    










                                                                                                    

 


                                                          
%%
%% %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).