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