1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2020. 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(ssh_renegotiate_SUITE).
24
25-include_lib("common_test/include/ct.hrl").
26-include_lib("kernel/include/inet.hrl").
27-include_lib("kernel/include/file.hrl").
28-include("ssh_test_lib.hrl").
29
30-export([
31         suite/0,
32         all/0,
33         groups/0,
34         init_per_suite/1,
35         end_per_suite/1,
36         init_per_group/2,
37         end_per_group/2
38        ]).
39
40-export([
41         norekey_limit_client/0,
42         norekey_limit_client/1,
43         norekey_limit_daemon/0,
44         norekey_limit_daemon/1,
45         rekey0/0,
46         rekey0/1,
47         rekey1/0,
48         rekey1/1,
49         rekey2/0,
50         rekey2/1,
51         rekey3/0,
52         rekey3/1,
53         rekey4/0,
54         rekey4/1,
55         rekey_limit_client/0,
56         rekey_limit_client/1,
57         rekey_limit_daemon/0,
58         rekey_limit_daemon/1,
59         rekey_time_limit_client/0,
60         rekey_time_limit_client/1,
61         rekey_time_limit_daemon/0,
62         rekey_time_limit_daemon/1,
63         renegotiate1/1,
64         renegotiate2/1
65        ]).
66
67-define(NEWLINE, <<"\r\n">>).
68
69-define(REKEY_DATA_TMO, 1 * 60000). % Should be multiples of 60000
70
71%%--------------------------------------------------------------------
72%% Common Test interface functions -----------------------------------
73%%--------------------------------------------------------------------
74
75suite() ->
76    [{ct_hooks,[ts_install_cth]},
77     {timetrap,{seconds,90}}].
78
79all() ->
80    [{group, renegotiate}
81    ].
82
83groups() ->
84    [{renegotiate, [parallel], [rekey0,
85                                rekey1,
86                                rekey2,
87                                rekey3,
88                                rekey4,
89                                rekey_limit_client,
90                                rekey_limit_daemon,
91                                rekey_time_limit_client,
92                                rekey_time_limit_daemon,
93                                norekey_limit_client,
94                                norekey_limit_daemon,
95                                renegotiate1,
96                                renegotiate2]}
97    ].
98
99%%--------------------------------------------------------------------
100init_per_suite(Config) ->
101    ?CHECK_CRYPTO(begin
102                      ssh:start(),
103                      ct:log("Pub keys setup for: ~p",
104                             [ssh_test_lib:setup_all_user_host_keys(Config)]),
105                      [{preferred_algorithms,ssh_transport:supported_algorithms()}
106                       | Config]
107                  end).
108
109end_per_suite(_Config) ->
110    ssh:stop().
111
112
113init_per_group(_, Config) -> Config.
114
115end_per_group(_, Config) -> Config.
116%%----------------------------------------------------------------------------
117%%% Idle timeout test
118rekey0() -> [{timetrap,{seconds,120}}].
119rekey1() -> [{timetrap,{seconds,120}}].
120rekey2() -> [{timetrap,{seconds,120}}].
121rekey3() -> [{timetrap,{seconds,120}}].
122rekey4() -> [{timetrap,{seconds,120}}].
123
124rekey0(Config) -> ssh_dbg:start(), ssh_dbg:on(renegotiation),
125                  R = rekey_chk(Config, 0,                   0),
126                  ssh_dbg:stop(),
127                  R.
128rekey1(Config) -> rekey_chk(Config, infinity,            0).
129rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0).
130rekey3(Config) -> rekey_chk(Config, 0,                   infinity).
131rekey4(Config) -> rekey_chk(Config, 0,                   {infinity,infinity}).
132
133rekey_chk(Config, RLdaemon, RLclient) ->
134    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,	[{rekey_limit, RLdaemon}]),
135    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]),
136    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
137
138    %% Make both sides send something:
139    {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef),
140
141    %% Check rekeying
142    timer:sleep(?REKEY_DATA_TMO),
143    ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
144
145    ssh:close(ConnectionRef),
146    ssh:stop_daemon(Pid).
147
148%%--------------------------------------------------------------------
149%%% Test rekeying by data volume
150
151rekey_limit_client() -> [{timetrap,{seconds,500}}].
152rekey_limit_client(Config) ->
153    Limit = 6000,
154    UserDir = proplists:get_value(priv_dir, Config),
155    DataFile = filename:join(UserDir, "rekey.data"),
156    Data = lists:duplicate(Limit+10,1),
157    Algs = proplists:get_value(preferred_algorithms, Config),
158    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
159							{preferred_algorithms,Algs}]),
160
161    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
162								  {max_random_length_padding,0}]),
163    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
164
165    %% Check that it doesn't rekey without data transfer
166    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
167    timer:sleep(?REKEY_DATA_TMO),
168    true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
169
170    %% Check that datatransfer triggers rekeying
171    ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
172    timer:sleep(?REKEY_DATA_TMO),
173    ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
174
175    %% Check that datatransfer continues to trigger rekeying
176    ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
177    timer:sleep(?REKEY_DATA_TMO),
178    ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
179
180    %% Check that it doesn't rekey without data transfer
181    timer:sleep(?REKEY_DATA_TMO),
182    true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
183
184    %% Check that it doesn't rekey on a small datatransfer
185    ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
186    timer:sleep(?REKEY_DATA_TMO),
187    true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
188
189    %% Check that it doesn't rekey without data transfer
190    timer:sleep(?REKEY_DATA_TMO),
191    true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
192
193    ssh_sftp:stop_channel(SftpPid),
194    ssh:close(ConnectionRef),
195    ssh:stop_daemon(Pid).
196
197
198
199rekey_limit_daemon() -> [{timetrap,{seconds,500}}].
200rekey_limit_daemon(Config) ->
201    Limit = 6000,
202    UserDir = proplists:get_value(priv_dir, Config),
203    DataFile1 = filename:join(UserDir, "rekey1.data"),
204    DataFile2 = filename:join(UserDir, "rekey2.data"),
205    file:write_file(DataFile1, lists:duplicate(Limit+10,1)),
206    file:write_file(DataFile2, "hi\n"),
207
208    Algs = proplists:get_value(preferred_algorithms, Config),
209    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
210                                                        {max_random_length_padding,0},
211							{preferred_algorithms,Algs}]),
212    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
213    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
214
215    %% Check that it doesn't rekey without data transfer
216    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
217    timer:sleep(?REKEY_DATA_TMO),
218    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
219
220    %% Check that datatransfer triggers rekeying
221    {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
222    timer:sleep(?REKEY_DATA_TMO),
223    ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
224
225    %% Check that datatransfer continues to trigger rekeying
226    {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
227    timer:sleep(?REKEY_DATA_TMO),
228    ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
229
230    %% Check that it doesn't rekey without data transfer
231    timer:sleep(?REKEY_DATA_TMO),
232    true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
233
234    %% Check that it doesn't rekey on a small datatransfer
235    {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2),
236    timer:sleep(?REKEY_DATA_TMO),
237    true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
238
239    %% Check that it doesn't rekey without data transfer
240    timer:sleep(?REKEY_DATA_TMO),
241    true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
242
243    ssh_sftp:stop_channel(SftpPid),
244    ssh:close(ConnectionRef),
245    ssh:stop_daemon(Pid).
246
247
248%%--------------------------------------------------------------------
249%% Check that datatransfer in the other direction does not trigger re-keying
250norekey_limit_client() -> [{timetrap,{seconds,500}}].
251norekey_limit_client(Config) ->
252    Limit = 6000,
253    UserDir = proplists:get_value(priv_dir, Config),
254    DataFile = filename:join(UserDir, "rekey3.data"),
255    file:write_file(DataFile, lists:duplicate(Limit+10,1)),
256
257    Algs = proplists:get_value(preferred_algorithms, Config),
258    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
259							{preferred_algorithms,Algs}]),
260
261    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
262								  {max_random_length_padding,0}]),
263    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
264
265    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
266    timer:sleep(?REKEY_DATA_TMO),
267    true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
268
269    {ok,_} = ssh_sftp:read_file(SftpPid, DataFile),
270    timer:sleep(?REKEY_DATA_TMO),
271    true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
272
273    ssh_sftp:stop_channel(SftpPid),
274    ssh:close(ConnectionRef),
275    ssh:stop_daemon(Pid).
276
277%% Check that datatransfer in the other direction does not trigger re-keying
278norekey_limit_daemon() -> [{timetrap,{seconds,500}}].
279norekey_limit_daemon(Config) ->
280    Limit = 6000,
281    UserDir = proplists:get_value(priv_dir, Config),
282    DataFile = filename:join(UserDir, "rekey4.data"),
283
284    Algs = proplists:get_value(preferred_algorithms, Config),
285    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
286                                                        {max_random_length_padding,0},
287							{preferred_algorithms,Algs}]),
288
289    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
290    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
291
292    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
293    timer:sleep(?REKEY_DATA_TMO),
294    true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
295
296    ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)),
297    timer:sleep(?REKEY_DATA_TMO),
298    true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
299
300    ssh_sftp:stop_channel(SftpPid),
301    ssh:close(ConnectionRef),
302    ssh:stop_daemon(Pid).
303
304%%--------------------------------------------------------------------
305%%% Test rekeying by time
306
307rekey_time_limit_client() -> [{timetrap,{seconds,500}}].
308rekey_time_limit_client(Config) ->
309    Minutes = ?REKEY_DATA_TMO div 60000,
310    GB = 1024*1000*1000,
311    Algs = proplists:get_value(preferred_algorithms, Config),
312    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
313							{preferred_algorithms,Algs}]),
314    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}},
315                                                                  {max_random_length_padding,0}]),
316    rekey_time_limit(Pid, ConnectionRef).
317
318rekey_time_limit_daemon() -> [{timetrap,{seconds,500}}].
319rekey_time_limit_daemon(Config) ->
320    Minutes = ?REKEY_DATA_TMO div 60000,
321    GB = 1024*1000*1000,
322    Algs = proplists:get_value(preferred_algorithms, Config),
323    {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}},
324                                                        {max_random_length_padding,0},
325							{preferred_algorithms,Algs}]),
326    ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
327    rekey_time_limit(Pid, ConnectionRef).
328
329
330rekey_time_limit(Pid, ConnectionRef) ->
331    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
332    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
333
334    timer:sleep(5000),
335    true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
336
337    %% Check that it rekeys when the max time + 30s has passed
338    timer:sleep(?REKEY_DATA_TMO + 30*1000),
339    ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
340
341    %% Check that it does not rekey when nothing is transferred
342    timer:sleep(?REKEY_DATA_TMO + 30*1000),
343    ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
344
345    ssh_sftp:stop_channel(SftpPid),
346    ssh:close(ConnectionRef),
347    ssh:stop_daemon(Pid).
348
349%%--------------------------------------------------------------------
350
351%%% Test rekeying with simultaneous send request
352
353renegotiate1(Config) ->
354    UserDir = proplists:get_value(priv_dir, Config),
355    DataFile = filename:join(UserDir, "renegotiate1.data"),
356
357    Algs = proplists:get_value(preferred_algorithms, Config),
358    {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
359							 {preferred_algorithms,Algs}]),
360
361    {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
362
363    ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
364    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
365
366    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
367
368    {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
369
370    ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
371
372    ssh_relay:hold(RelayPid, rx, 20, 1000),
373    ssh_connection_handler:renegotiate(ConnectionRef),
374    spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
375
376    timer:sleep(2000),
377
378    Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
379
380    false = (Kex2 == Kex1),
381
382    ssh_relay:stop(RelayPid),
383    ssh_sftp:stop_channel(SftpPid),
384    ssh:close(ConnectionRef),
385    ssh:stop_daemon(Pid).
386
387%%--------------------------------------------------------------------
388
389%%% Test rekeying with inflight messages from peer
390
391renegotiate2(Config) ->
392    UserDir = proplists:get_value(priv_dir, Config),
393    DataFile = filename:join(UserDir, "renegotiate2.data"),
394
395    Algs = proplists:get_value(preferred_algorithms, Config),
396    {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
397							 {preferred_algorithms,Algs}]),
398
399    {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
400
401    ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
402    {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
403
404    Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
405
406    {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
407
408    ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
409
410    ssh_relay:hold(RelayPid, rx, 20, infinity),
411    spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
412    %% need a small pause here to ensure ssh_sftp:write is executed
413    ct:sleep(10),
414    ssh_connection_handler:renegotiate(ConnectionRef),
415    ssh_relay:release(RelayPid, rx),
416
417    timer:sleep(2000),
418
419    Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
420
421    false = (Kex2 == Kex1),
422
423    ssh_relay:stop(RelayPid),
424    ssh_sftp:stop_channel(SftpPid),
425    ssh:close(ConnectionRef),
426    ssh:stop_daemon(Pid).
427
428%%--------------------------------------------------------------------
429%% Internal functions ------------------------------------------------
430%%--------------------------------------------------------------------
431