aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/test/ssl_sni_SUITE.erl
blob: e68ea2c99d79453dee533cb349f1bbb29566d547 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
%%
%% %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.