aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssl/test/ssl_sni_SUITE.erl
blob: e68ea2c99d79453dee533cb349f1bbb29566d547 (plain) (tree)
1
2
3
4
5


                   
                                                        
  










                                                                           


                 







                                                  

                                        



                                                                      
 



                        








                                  









                                   


                      
                    

                               




                          
                                       











                                                                                                               
                 






                                          
                                                      
                                                            

                               
           
                                       
                                                            






                                                     



                                                                      
                                                                    
 
                            
                                                                            
 
                    
                                                                       
 
                        
                                                                              
 
                       
                                                                     
 
                           
                                                                             
 



                                                                                 

                                                                                                                                    


                                                                                                                                              

                                                                                                                                
                                                                                 


                                                                                                                                    



                                                                                                                                 
 





                                                                                    

                                                                                                                                    



                                                                                                                                            

                                                                                                                               
                                                                                 


                                                                                                                                    

                                                                                            
 




                                                                                    

                                                                                                                                    


                                                                                                                                             

                                                                                                                               

                                                                                  


                                                                                                                                    

                                                                                            
 



                                                                                 

                                                                                                                                    


                                                                                                                                                 



                                                                                                                             
                                                                                 


                                                                                                                                    
















                                                                                                                        



                      











                                                                                                            





















































                                                                                                                      
 


                                                                      














                                                               
                 


                                                         

                               





                                                                  
 





                                                                               








                                             

                                                                                               


                                                                         
                                                                              


                                                                               
                                                                                
                                                                                      
                                                                                              


                       
                                                              
            
                                                                                                        










                                                                                                


                                                                               

                                                                      


                                                                   
                                                                                                                      
                    

                           
                                                                  
                
                                                                                                            
            









                                                                                                


                                                                               



























                                                                                                           

                                                                       



                                 
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2015-2018. 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_sni_SUITE).

-compile(export_all).

-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/inet.hrl").


%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

all() ->
    [{group, 'tlsv1.2'},
     {group, 'tlsv1.1'},
     {group, 'tlsv1'},
     {group, 'dtlsv1.2'},
     {group, 'dtlsv1'}
    ].

groups() ->
    [
     {'tlsv1.2', [], sni_tests()},
     {'tlsv1.1', [], sni_tests()},
     {'tlsv1', [], sni_tests()},
     {'dtlsv1.2', [], sni_tests()},
     {'dtlsv1', [], sni_tests()}
    ].

sni_tests() ->
    [no_sni_header, 
     sni_match, 
     sni_no_match,
     no_sni_header_fun, 
     sni_match_fun, 
     sni_no_match_fun,
     dns_name,
     ip_fallback,
     no_ip_fallback,
     dns_name_reuse,
     customize_hostname_check].

init_per_suite(Config0) ->
    catch crypto:stop(),
    try crypto:start() of
        ok ->
            ssl_test_lib:clean_start(),
            Config = ssl_test_lib:make_rsa_cert(Config0),
            RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
            [{sni_server_opts, [{sni_hosts, [
                                              {"a.server", [
                                                            {certfile, proplists:get_value(certfile, RsaOpts)},
                                                            {keyfile,  proplists:get_value(keyfile, RsaOpts)}
                                                           ]},
                                              {"b.server", [
                                                            {certfile, proplists:get_value(certfile, RsaOpts)},
                                                            {keyfile, proplists:get_value(keyfile, RsaOpts)}
                                                           ]}
                                             ]}]} | Config] 
    catch _:_  ->
            {skip, "Crypto did not start"}
    end.

end_per_suite(_) ->
    ssl:stop(),
    application:stop(crypto).

init_per_testcase(customize_hostname_check, Config) ->
    ssl_test_lib:ct_log_supported_protocol_versions(Config),
    ssl_test_lib:clean_start(),
    ct:timetrap({seconds, 5}),
    Config;
init_per_testcase(_TestCase, Config) ->
    ssl_test_lib:ct_log_supported_protocol_versions(Config),
    ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
    ct:timetrap({seconds, 5}),
    Config.

end_per_testcase(_TestCase, Config) ->     
    Config.

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
no_sni_header(Config) ->
    run_handshake(Config, undefined, undefined, "server Peer cert").

no_sni_header_fun(Config) ->
    run_sni_fun_handshake(Config, undefined, undefined, "server Peer cert").

sni_match(Config) ->
    run_handshake(Config, "a.server", "a.server",  "server Peer cert").

sni_match_fun(Config) ->
    run_sni_fun_handshake(Config, "a.server", "a.server", "server Peer cert").

sni_no_match(Config) ->
    run_handshake(Config, "c.server", undefined, "server Peer cert").

sni_no_match_fun(Config) ->
    run_sni_fun_handshake(Config, "c.server", undefined, "server Peer cert").

dns_name(Config) ->
    Hostname = "OTP.test.server",
    #{server_config := ServerConf,
      client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
                                                                       peer => [{extensions,  [#'Extension'{extnID = 
                                                                                                                ?'id-ce-subjectAltName',
                                                                                                            extnValue = [{dNSName, Hostname}],
                                                                                                            critical = false}]},
                                                                                {key, ssl_test_lib:hardcode_rsa_key(3)}]},
                                                                 client_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
                                                                       peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
    unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
    successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, Hostname} | ClientConf], undefined, Config),
    unsuccessfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf], undefined, Config),
    successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf], undefined, Config).

ip_fallback(Config) ->
    Hostname = net_adm:localhost(),
    {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
    IPStr = tuple_to_list(IP),
    #{server_config := ServerConf,
      client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
                                                                       peer => [{extensions, [#'Extension'{extnID = 
                                                                                                               ?'id-ce-subjectAltName',
                                                                                                           extnValue = [{dNSName, Hostname},
                                                                                                                        {iPAddress, IPStr}],
                                                                                                           critical = false}]},
                                                                                {key, ssl_test_lib:hardcode_rsa_key(3)}]},
                                                                 client_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
                                                                       peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
    successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
    successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).

no_ip_fallback(Config) ->
    Hostname = net_adm:localhost(),
    {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
    #{server_config := ServerConf,
      client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
                                                                       peer => [{extensions, [#'Extension'{extnID = 
                                                                                                               ?'id-ce-subjectAltName',
                                                                                                           extnValue = [{dNSName, Hostname}],
                                                                                                           critical = false}]},
                                                                                {key, ssl_test_lib:hardcode_rsa_key(3)}
                                                                               ]},
                                                                 client_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
                                                                       peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
    successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
    unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).

dns_name_reuse(Config) ->
    SNIHostname = "OTP.test.server",
    #{server_config := ServerConf,
      client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
                                                                       peer => [{extensions,  [#'Extension'{extnID = 
                                                                                                                ?'id-ce-subjectAltName',
                                                                                                            extnValue = [{dNSName, SNIHostname}],
                                                                                                            critical = false}
                                                                                              ]},
                                                                                {key, ssl_test_lib:hardcode_rsa_key(3)}
                                                                               ]},
                                                                 client_chain => 
                                                                     #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
                                                                       intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
                                                                       peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
    
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
  
    unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
    
    Server = 
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, 
				   {from, self()},
				   {mfa, {ssl_test_lib, session_info_result, []}},
				   {options, ServerConf}]),
    Port = ssl_test_lib:inet_port(Server),
    Client0 =
	ssl_test_lib:start_client([{node, ClientNode}, 
                                   {port, Port}, {host, Hostname},
				   {mfa, {ssl_test_lib, no_result, []}},
                                   {from, self()}, {options, [{verify, verify_peer}, 
                                                              {server_name_indication, SNIHostname} | ClientConf]}]),   
    receive
        {Server, _} ->
            ok
    end,
    
    Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
    
    %% Make sure session is registered
    ct:sleep(1000),
    
    Client1 =
	ssl_test_lib:start_client_error([{node, ClientNode},
                                         {port, Port}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_info_result, []}},
                                         {from, self()},  {options, [{verify, verify_peer} | ClientConf]}]),
    
    ssl_test_lib:check_client_alert(Client1, handshake_failure),
    ssl_test_lib:close(Client0).


customize_hostname_check() ->
    [{doc,"Test option customize_hostname_check."}].
customize_hostname_check(Config) when is_list(Config) ->
    Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName',
                        extnValue = [{dNSName, "*.example.org"}],
                        critical = false}
          ],
    #{server_config := ServerOpts0,
      client_config := ClientOpts0} = ssl_test_lib:make_cert_chains_pem(rsa, [{server_chain,
                                                                               [[], 
                                                                                [],
                                                                                [{extensions, Ext}]
                                                                               ]}], 
                                                                        Config, "https_hostname_convention"),
    ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config),
    ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config),  
                                        
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
  
    Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, 
                                        {host, Hostname},
					{from, self()}, 
					{mfa, {ssl_test_lib, send_recv_result_active, []}},
					{options, ServerOpts}]),
    Port = ssl_test_lib:inet_port(Server),
    
    CustomFun = public_key:pkix_verify_hostname_match_fun(https),
    
    Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, 
                                        {host, Hostname},
					 {from, self()}, 
                                        {mfa, {ssl_test_lib, send_recv_result_active, []}},
                                        {options,
                                         [{verify, verify_peer},
                                          {server_name_indication, "other.example.org"},
                                          {customize_hostname_check, 
                                           [{match_fun, CustomFun}]} | ClientOpts]
					 }]),    
    ssl_test_lib:check_result(Server, ok, Client, ok),
    
    Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
                                        
    Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, 
                                               {host, Hostname},
                                               {from, self()}, 
                                               {mfa, {ssl_test_lib, no_result, []}},
                                               {options, [{verify, verify_peer},
                                                          {server_name_indication, "other.example.org"} | ClientOpts]}
                                              ]),    
    ssl_test_lib:check_client_alert(Server, Client1, handshake_failure).

%%--------------------------------------------------------------------
%% Internal Functions ------------------------------------------------
%%--------------------------------------------------------------------
ssl_recv(SSLSocket, Expect) ->
    ssl_recv(SSLSocket, "", Expect).

ssl_recv(SSLSocket, CurrentData, ExpectedData) ->
    receive
        {ssl, SSLSocket, Data} ->
            NeweData = CurrentData ++ Data,
            case NeweData of
                ExpectedData ->
                    ok;
                _  ->
                    ssl_recv(SSLSocket, NeweData, ExpectedData)
            end;
        Other ->
            ct:fail({unexpected_message, Other})
    after 4000 ->
            ct:fail({timeout, CurrentData, ExpectedData})
    end.

send_and_hostname(SSLSocket) ->
    ssl:send(SSLSocket, "OK"),
    case  ssl:connection_information(SSLSocket, [sni_hostname]) of
	{ok, [{sni_hostname, Hostname}]} ->
	    Hostname;
	{ok, []} ->
	    undefined
    end.

rdnPart([[#'AttributeTypeAndValue'{type=Type, value=Value} | _] | _], Type) -> 
    Value;
rdnPart([_ | Tail], Type) -> 
    rdnPart(Tail, Type);
rdnPart([], _) -> 
    unknown.

rdn_to_string({utf8String, Binary}) ->
    erlang:binary_to_list(Binary);
rdn_to_string({printableString, String}) ->
    String.

recv_and_certificate(SSLSocket) ->
    ssl_recv(SSLSocket, "OK"),
    {ok, PeerCert} = ssl:peercert(SSLSocket),
    #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = {rdnSequence, Subject}}} 
	= public_key:pkix_decode_cert(PeerCert, otp),
    ct:log("Subject of certificate received from server: ~p", [Subject]),
    rdn_to_string(rdnPart(Subject, ?'id-at-commonName')).

run_sni_fun_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) ->
    ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, "
	   "ExpectedSNIHostname: ~p, ExpectedCN: ~p", 
	   [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]),
    [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config),
    SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end,
    ServerOptions =  ssl_test_lib:ssl_options(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}],
    ClientOptions = 
    case SNIHostname of
        undefined ->
            ssl_test_lib:ssl_options(client_rsa_opts, Config);
        _ ->
            [{server_name_indication, SNIHostname}] ++ ssl_test_lib:ssl_options(client_rsa_opts, Config)
    end,
    ct:log("Options: ~p", [[ServerOptions, ClientOptions]]),
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
                                        {from, self()}, {mfa, {?MODULE, send_and_hostname, []}},
                                        {options, ServerOptions}]),
    Port = ssl_test_lib:inet_port(Server),
    Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
                                        {host, Hostname}, {from, self()},
                                        {mfa, {?MODULE, recv_and_certificate, []}},
                                        {options, ClientOptions}]),
    ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN),
    ssl_test_lib:close(Server),
    ssl_test_lib:close(Client).

run_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) ->
    ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, "
	   "ExpectedSNIHostname: ~p, ExpectedCN: ~p", 
	   [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]),
    ServerOptions = proplists:get_value(sni_server_opts, Config) ++ ssl_test_lib:ssl_options(server_rsa_opts, Config),
    ClientOptions = 
        case SNIHostname of
            undefined ->
                ssl_test_lib:ssl_options(client_rsa_opts, Config);
            _ ->
                [{server_name_indication, SNIHostname}] ++ ssl_test_lib:ssl_options(client_rsa_opts, Config)
        end,
    ct:log("Options: ~p", [[ServerOptions, ClientOptions]]),
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
                                        {from, self()}, {mfa, {?MODULE, send_and_hostname, []}},
                                        {options, ServerOptions}]),
    Port = ssl_test_lib:inet_port(Server),
    Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
                                        {host, Hostname}, {from, self()},
                                        {mfa, {?MODULE, recv_and_certificate, []}},
                                        {options, ClientOptions}]),
    ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN),
    ssl_test_lib:close(Server),
    ssl_test_lib:close(Client).

successfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->  
    {ClientNode, ServerNode, Hostname1} = ssl_test_lib:run_where(Config),
    Hostname = host_name(Hostname0, Hostname1),
    Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
                                        {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}},
                                        {options, ServerOptions}]),
    Port = ssl_test_lib:inet_port(Server),
    Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
                                        {host, Hostname}, {from, self()},
                                        {mfa, {ssl_test_lib, send_recv_result_active, []}},
                                        {options, ClientOptions}]),
    ssl_test_lib:check_result(Server, ok, Client, ok),
    ssl_test_lib:close(Server),
    ssl_test_lib:close(Client).

unsuccessfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
    {ClientNode, ServerNode, Hostname1} = ssl_test_lib:run_where(Config),
    Hostname = host_name(Hostname0, Hostname1),
    Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
					      {from, self()},
					      {options, ServerOptions}]),
    Port = ssl_test_lib:inet_port(Server),
    Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
					      {host, Hostname},
					      {from, self()}, 
					      {options, ClientOptions}]),
    
    ssl_test_lib:check_server_alert(Server, Client, handshake_failure).

host_name(undefined, Hostname) ->
    Hostname;
host_name(Hostname, _) ->
    Hostname.