1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22
23-module(ssl_session_cache_SUITE).
24
25%% Note: This directive should only be used in test suites.
26-compile(export_all).
27
28-include_lib("common_test/include/ct.hrl").
29
30-define(DELAY, 500).
31-define(SLEEP, 500).
32-define(TIMEOUT, 60000).
33-define(LONG_TIMEOUT, 600000).
34-define(MAX_TABLE_SIZE, 5).
35
36-behaviour(ssl_session_cache_api).
37
38%% For the session cache tests
39-export([init/1, terminate/1, lookup/2, update/3,
40	 delete/2, foldl/3, select_session/2]).
41
42%%--------------------------------------------------------------------
43%% Common Test interface functions -----------------------------------
44%%--------------------------------------------------------------------
45
46all() ->
47    [session_cleanup,
48     session_cache_process_list,
49     session_cache_process_mnesia,
50     client_unique_session,
51     max_table_size,
52     save_specific_session
53     ].
54
55groups() ->
56    [].
57
58init_per_suite(Config0) ->
59    catch crypto:stop(),
60    try crypto:start() of
61	ok ->
62	    ssl_test_lib:clean_start(),
63	    %% make rsa certs using
64            ssl_test_lib:make_rsa_cert(Config0)
65    catch _:_ ->
66	    {skip, "Crypto did not start"}
67    end.
68
69end_per_suite(_Config) ->
70    ssl:stop(),
71    application:stop(crypto).
72
73init_per_group(_GroupName, Config) ->
74    Config.
75
76end_per_group(_GroupName, Config) ->
77    Config.
78
79init_per_testcase(session_cache_process_list, Config) ->
80    init_customized_session_cache(list, Config);
81
82init_per_testcase(session_cache_process_mnesia, Config) ->
83    mnesia:start(),
84    init_customized_session_cache(mnesia, Config);
85
86init_per_testcase(session_cleanup, Config) ->
87    ssl:stop(),
88    application:load(ssl),
89    application:set_env(ssl, session_lifetime, 5),
90    application:set_env(ssl, session_delay_cleanup_time, ?DELAY),
91    ssl:start(),
92    ct:timetrap({seconds, 20}),
93    Config;
94
95init_per_testcase(client_unique_session, Config) ->
96    ct:timetrap({seconds, 40}),
97    Config;
98init_per_testcase(save_specific_session, Config) ->
99    ssl_test_lib:clean_start(),
100    ct:timetrap({seconds, 5}),
101    Config;
102init_per_testcase(max_table_size, Config) ->
103    ssl:stop(),
104    application:load(ssl),
105    application:set_env(ssl, session_cache_server_max, ?MAX_TABLE_SIZE),
106    application:set_env(ssl, session_cache_client_max, ?MAX_TABLE_SIZE),
107    application:set_env(ssl, session_delay_cleanup_time, ?DELAY),
108    ssl:start(),
109    ct:timetrap({seconds, 40}),
110    Config.
111
112init_customized_session_cache(Type, Config) ->
113    ssl:stop(),
114    application:load(ssl),
115    application:set_env(ssl, session_cb, ?MODULE),
116    application:set_env(ssl, session_cb_init_args, [{type, Type}]),
117    ssl:start(),
118    catch (end_per_testcase(list_to_atom("session_cache_process" ++ atom_to_list(Type)),
119	   Config)),
120    ets:new(ssl_test, [named_table, public, set]),
121    ets:insert(ssl_test, {type, Type}),
122    ct:timetrap({seconds, 20}),
123    Config.
124
125end_per_testcase(session_cache_process_list, Config) ->
126    application:unset_env(ssl, session_cb),
127    end_per_testcase(default_action, Config);
128end_per_testcase(session_cache_process_mnesia, Config) ->
129    application:unset_env(ssl, session_cb),
130    application:unset_env(ssl, session_cb_init_args),
131    mnesia:kill(),
132    ssl:stop(),
133    ssl:start(),
134    end_per_testcase(default_action, Config);
135end_per_testcase(session_cleanup, Config) ->
136    application:unset_env(ssl, session_delay_cleanup_time),
137    application:unset_env(ssl, session_lifetime),
138    end_per_testcase(default_action, Config);
139end_per_testcase(max_table_size, Config) ->
140    application:unset_env(ssl, session_cach_server_max),
141    application:unset_env(ssl, session_cach_client_max),
142    end_per_testcase(default_action, Config);
143end_per_testcase(Case, Config) when Case == session_cache_process_list;
144				    Case == session_cache_process_mnesia ->
145    catch ets:delete(ssl_test),
146    Config;
147end_per_testcase(_, Config) ->
148    Config.
149
150%%--------------------------------------------------------------------
151%% Test Cases --------------------------------------------------------
152%%--------------------------------------------------------------------
153client_unique_session() ->
154    [{doc, "Test session table does not grow when client "
155      "sets up many connections"}].
156client_unique_session(Config) when is_list(Config) ->
157    process_flag(trap_exit, true),
158    ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
159    ServerOpts = proplists:get_value(server_rsa_opts, Config),
160    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
161    Server =
162	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
163				   {from, self()},
164				   {mfa, {ssl_test_lib, no_result, []}},
165				   {tcp_options, [{active, false}]},
166				   {options, ServerOpts}]),
167    Port = ssl_test_lib:inet_port(Server),
168    LastClient = clients_start(Server, ClientNode, Hostname, Port, ClientOpts, 20),
169    receive
170	{LastClient, {ok, _}} ->
171	    ok
172    end,
173    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
174    [_, _,_, _, Prop] = StatusInfo,
175    State = ssl_test_lib:state(Prop),
176    ClientCache = element(2, State),
177
178    1 = ssl_session_cache:size(ClientCache),
179
180    ssl_test_lib:close(Server, 500),
181    ssl_test_lib:close(LastClient).
182
183session_cleanup() ->
184    [{doc, "Test that sessions are cleand up eventually, so that the session table "
185     "does not grow and grow ..."}].
186session_cleanup(Config) when is_list(Config) ->
187    process_flag(trap_exit, true),
188    ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
189    ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
190    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
191
192    Server =
193	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
194				   {from, self()},
195				   {mfa, {ssl_test_lib, session_info_result, []}},
196				   {options, ServerOpts}]),
197    Port = ssl_test_lib:inet_port(Server),
198    Client =
199	ssl_test_lib:start_client([{node, ClientNode},
200				   {port, Port}, {host, Hostname},
201				   {mfa, {ssl_test_lib, no_result, []}},
202				   {from, self()},  {options, ClientOpts}]),
203    SessionInfo =
204	receive
205	    {Server, Info} ->
206		Info
207	end,
208
209    %% Make sure session is registered
210    ct:sleep(?SLEEP),
211
212    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
213    [_, _,_, _, Prop] = StatusInfo,
214    State = ssl_test_lib:state(Prop),
215    ClientCache = element(2, State),
216    ServerCache = element(3, State),
217    SessionTimer = element(7, State),
218
219    Id = proplists:get_value(session_id, SessionInfo),
220    CSession = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
221    SSession = ssl_session_cache:lookup(ServerCache, {Port, Id}),
222
223    true = CSession =/= undefined,
224    true = SSession =/= undefined,
225
226    %% Make sure session has expired and been cleaned up
227    check_timer(SessionTimer),
228    ct:sleep(?DELAY *2),  %% Delay time + some extra time
229
230    {ServerDelayTimer, ClientDelayTimer} = get_delay_timers(),
231
232    check_timer(ServerDelayTimer),
233    check_timer(ClientDelayTimer),
234
235    ct:sleep(?SLEEP),  %% Make sure clean has had time to run
236
237    undefined = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
238    undefined = ssl_session_cache:lookup(ServerCache, {Port, Id}),
239
240    process_flag(trap_exit, false),
241    ssl_test_lib:close(Server),
242    ssl_test_lib:close(Client).
243
244
245%%--------------------------------------------------------------------
246session_cache_process_list() ->
247    [{doc,"Test reuse of sessions (short handshake)"}].
248session_cache_process_list(Config) when is_list(Config) ->
249    session_cache_process(list,Config).
250%%--------------------------------------------------------------------
251session_cache_process_mnesia() ->
252    [{doc,"Test reuse of sessions (short handshake)"}].
253session_cache_process_mnesia(Config) when is_list(Config) ->
254    session_cache_process(mnesia,Config).
255
256%%--------------------------------------------------------------------
257save_specific_session() ->
258    [{doc, "Test that we can save a specific client session"
259     }].
260save_specific_session(Config) when is_list(Config) ->
261    process_flag(trap_exit, true),
262    ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
263    ServerOpts = proplists:get_value(server_rsa_opts, Config),
264    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
265    Server =
266	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
267				   {from, self()},
268				   {mfa, {ssl_test_lib, no_result, []}},
269				   {tcp_options, [{active, false}]},
270				   {options, ServerOpts}]),
271    Port = ssl_test_lib:inet_port(Server),
272
273    Client1 = ssl_test_lib:start_client([{node, ClientNode},
274                                         {port, Port}, {host, Hostname},
275                                         {mfa, {ssl_test_lib, session_id, []}},
276                                         {from, self()},  {options, ClientOpts}]),
277    Server ! listen,
278
279    Client2 = ssl_test_lib:start_client([{node, ClientNode},
280                                         {port, Port}, {host, Hostname},
281                                         {mfa, {ssl_test_lib, session_id, []}},
282                                         {from, self()},  {options, [{reuse_sessions, save} | ClientOpts]}]),
283    SessionID1 =
284        receive
285            {Client1, S1} ->
286                S1
287        end,
288
289    SessionID2 =
290        receive
291            {Client2, S2} ->
292                S2
293        end,
294
295    true = SessionID1 =/= SessionID2,
296
297    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
298    [_, _,_, _, Prop] = StatusInfo,
299    State = ssl_test_lib:state(Prop),
300    ClientCache = element(2, State),
301    2 = ssl_session_cache:size(ClientCache),
302
303    Server ! listen,
304
305    Client3 = ssl_test_lib:start_client([{node, ClientNode},
306                                         {port, Port}, {host, Hostname},
307                                         {mfa, {ssl_test_lib, session_id, []}},
308                                         {from, self()},  {options, [{reuse_session, SessionID2} | ClientOpts]}]),
309    receive
310        {Client3, SessionID2} ->
311            ok;
312        {Client3, SessionID3}->
313            ct:fail({got, SessionID3, expected, SessionID2});
314        Other ->
315            ct:fail({got,Other})
316    end.
317
318%%--------------------------------------------------------------------
319
320max_table_size() ->
321    [{doc,"Test max limit on session table"}].
322max_table_size(Config) when is_list(Config) ->
323    process_flag(trap_exit, true),
324    ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
325    ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
326    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
327    Server =
328	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
329				   {from, self()},
330				   {mfa, {ssl_test_lib, no_result, []}},
331				   {tcp_options, [{active, false}]},
332				   {options, ServerOpts}]),
333    Port = ssl_test_lib:inet_port(Server),
334    LastClient = clients_start(Server,
335			    ClientNode, Hostname, Port, ClientOpts, 20),
336    receive
337	{LastClient, {ok, _}} ->
338	    ok
339    end,
340    ct:sleep(1000),
341    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
342    [_, _,_, _, Prop] = StatusInfo,
343    State = ssl_test_lib:state(Prop),
344    ClientCache = element(2, State),
345    ServerCache = element(3, State),
346    N = ssl_session_cache:size(ServerCache),
347    M = ssl_session_cache:size(ClientCache),
348    ct:pal("~p",[{N, M}]),
349    ssl_test_lib:close(Server, 500),
350    ssl_test_lib:close(LastClient),
351    true = N =< ?MAX_TABLE_SIZE,
352    true = M =< ?MAX_TABLE_SIZE.
353
354%%--------------------------------------------------------------------
355%%% Session cache API callbacks
356%%--------------------------------------------------------------------
357
358init(Opts) ->
359    case proplists:get_value(type, Opts) of
360	list ->
361	    spawn(fun() -> session_loop([]) end);
362	mnesia ->
363	    mnesia:start(),
364	    Name = atom_to_list(proplists:get_value(role, Opts)),
365	    TabName = list_to_atom(Name ++ "sess_cache"),
366	    {atomic,ok} = mnesia:create_table(TabName, []),
367	    TabName
368    end.
369
370session_cb() ->
371    [{type, Type}] = ets:lookup(ssl_test, type),
372    Type.
373
374terminate(Cache) ->
375    case session_cb() of
376	list ->
377	    Cache ! terminate;
378	mnesia ->
379	    catch {atomic,ok} =
380		mnesia:delete_table(Cache)
381    end.
382
383lookup(Cache, Key) ->
384    case session_cb() of
385	list ->
386	    Cache ! {self(), lookup, Key},
387	    receive {Cache, Res} -> Res end;
388	mnesia ->
389	    case mnesia:transaction(fun() ->
390					    mnesia:read(Cache,
391							Key, read)
392				    end) of
393		{atomic, [{Cache, Key, Value}]} ->
394		    Value;
395		_ ->
396		    undefined
397	    end
398	end.
399
400update(Cache, Key, Value) ->
401    case session_cb() of
402	list ->
403	    Cache ! {update, Key, Value};
404	mnesia ->
405	    {atomic, ok} =
406		mnesia:transaction(fun() ->
407					   mnesia:write(Cache,
408							{Cache, Key, Value}, write)
409				   end)
410    end.
411
412delete(Cache, Key) ->
413    case session_cb() of
414	list ->
415	    Cache ! {delete, Key};
416	mnesia ->
417	    {atomic, ok} =
418		mnesia:transaction(fun() ->
419					   mnesia:delete(Cache, Key)
420				   end)
421    end.
422
423foldl(Fun, Acc, Cache) ->
424    case session_cb() of
425	list ->
426	    Cache ! {self(),foldl,Fun,Acc},
427	    receive {Cache, Res} -> Res end;
428	mnesia ->
429	    Foldl = fun() ->
430			    mnesia:foldl(Fun, Acc, Cache)
431		    end,
432	    {atomic, Res} = mnesia:transaction(Foldl),
433	    Res
434    end.
435
436select_session(Cache, PartialKey) ->
437    case session_cb() of
438	list ->
439	    Cache ! {self(),select_session, PartialKey},
440	    receive
441		{Cache, Res} ->
442		    Res
443	    end;
444	mnesia ->
445	    Sel = fun() ->
446			  mnesia:select(Cache,
447					[{{Cache,{PartialKey,'_'}, '$1'},
448					  [],['$1']}])
449		  end,
450	    {atomic, Res} = mnesia:transaction(Sel),
451	    Res
452    end.
453
454session_loop(Sess) ->
455    receive
456	terminate ->
457	    ok;
458	{Pid, lookup, Key} ->
459	    case lists:keysearch(Key,1,Sess) of
460		{value, {Key,Value}} ->
461		    Pid ! {self(), Value};
462		_ ->
463		    Pid ! {self(), undefined}
464	    end,
465	    session_loop(Sess);
466	{update, Key, Value} ->
467	    NewSess = [{Key,Value}| lists:keydelete(Key,1,Sess)],
468	    session_loop(NewSess);
469	{delete, Key} ->
470	    session_loop(lists:keydelete(Key,1,Sess));
471	{Pid,foldl,Fun,Acc} ->
472	    Res = lists:foldl(Fun, Acc,Sess),
473	    Pid ! {self(), Res},
474	    session_loop(Sess);
475	{Pid,select_session,PKey} ->
476	    Sel = fun({{PKey0, _Id},Session}, Acc) when PKey == PKey0 ->
477			  [Session | Acc];
478		     (_,Acc) ->
479			  Acc
480		  end,
481	    Sessions = lists:foldl(Sel, [], Sess),
482	    Pid ! {self(), Sessions},
483	    session_loop(Sess)
484    end.
485
486%%--------------------------------------------------------------------
487%%% Internal functions
488%%--------------------------------------------------------------------
489
490session_cache_process(_Type,Config) when is_list(Config) ->
491    ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
492    ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
493    ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
494
495
496clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) ->
497    %% Make sure session is registered
498    ct:sleep(?SLEEP * 2),
499    ssl_test_lib:start_client([{node, ClientNode},
500			       {port, Port}, {host, Hostname},
501			       {mfa, {?MODULE, connection_info_result, []}},
502			       {from, self()},  {options, ClientOpts}]);
503clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) ->
504    spawn_link(ssl_test_lib, start_client,
505	       [[{node, ClientNode},
506		 {port, Port}, {host, Hostname},
507		 {mfa, {ssl_test_lib, no_result, []}},
508		 {from, self()},  {options, ClientOpts}]]),
509    Server ! listen,
510    wait_for_server(),
511    clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1).
512
513connection_info_result(Socket) ->
514    ssl:connection_information(Socket, [protocol, cipher_suite]).
515
516check_timer(Timer) ->
517    case erlang:read_timer(Timer) of
518	false ->
519	    {status, _, _, _} = sys:get_status(whereis(ssl_manager)),
520	    timer:sleep(?SLEEP),
521	    {status, _, _, _} = sys:get_status(whereis(ssl_manager)),
522	    ok;
523	Int ->
524	    ct:sleep(Int),
525	    check_timer(Timer)
526    end.
527
528get_delay_timers() ->
529    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
530    [_, _,_, _, Prop] = StatusInfo,
531    State = ssl_test_lib:state(Prop),
532    case element(8, State) of
533	{undefined, undefined} ->
534	    ct:sleep(?SLEEP),
535	    get_delay_timers();
536	{undefined, _} ->
537	    ct:sleep(?SLEEP),
538	    get_delay_timers();
539	{_, undefined} ->
540	    ct:sleep(?SLEEP),
541	    get_delay_timers();
542	DelayTimers ->
543	    DelayTimers
544    end.
545
546wait_for_server() ->
547    ct:sleep(100).
548