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