diff options
Diffstat (limited to 'lib/ssh/test/ssh_options_SUITE.erl')
-rw-r--r-- | lib/ssh/test/ssh_options_SUITE.erl | 282 |
1 files changed, 213 insertions, 69 deletions
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index ba0107efd6..eedb2b389d 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% Copyright Ericsson AB 2008-2017. 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. @@ -27,7 +27,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). - +-include("ssh_test_lib.hrl"). %%% Test cases -export([connectfun_disconnectfun_client/1, @@ -51,8 +51,8 @@ ssh_connect_arg4_timeout/1, ssh_connect_negtimeout_parallel/1, ssh_connect_negtimeout_sequential/1, - ssh_connect_nonegtimeout_connected_parallel/1, - ssh_connect_nonegtimeout_connected_sequential/1, + ssh_connect_nonegtimeout_connected_parallel/1, + ssh_connect_nonegtimeout_connected_sequential/1, ssh_connect_timeout/1, connect/4, ssh_daemon_minimal_remote_max_packet_size_option/1, ssh_msg_debug_fun_option_client/1, @@ -61,7 +61,14 @@ unexpectedfun_option_client/1, unexpectedfun_option_server/1, user_dir_option/1, - connectfun_disconnectfun_server/1 + connectfun_disconnectfun_server/1, + hostkey_fingerprint_check/1, + hostkey_fingerprint_check_md5/1, + hostkey_fingerprint_check_sha/1, + hostkey_fingerprint_check_sha256/1, + hostkey_fingerprint_check_sha384/1, + hostkey_fingerprint_check_sha512/1, + hostkey_fingerprint_check_list/1 ]). %%% Common test callbacks @@ -80,7 +87,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}, - {timetrap,{minutes,6}}]. + {timetrap,{seconds,30}}]. all() -> [connectfun_disconnectfun_server, @@ -100,6 +107,13 @@ all() -> disconnectfun_option_client, unexpectedfun_option_server, unexpectedfun_option_client, + hostkey_fingerprint_check, + hostkey_fingerprint_check_md5, + hostkey_fingerprint_check_sha, + hostkey_fingerprint_check_sha256, + hostkey_fingerprint_check_sha384, + hostkey_fingerprint_check_sha512, + hostkey_fingerprint_check_list, id_string_no_opt_client, id_string_own_string_client, id_string_random_client, @@ -126,19 +140,19 @@ groups() -> %%-------------------------------------------------------------------- init_per_suite(Config) -> - Config. + ?CHECK_CRYPTO(Config). end_per_suite(_Config) -> ssh:stop(). %%-------------------------------------------------------------------- init_per_group(hardening_tests, Config) -> - DataDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), ssh_test_lib:setup_dsa(DataDir, PrivDir), Config; init_per_group(dir_options, Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), %% Make unreadable dir: Dir_unreadable = filename:join(PrivDir, "unread"), ok = file:make_dir(Dir_unreadable), @@ -193,7 +207,7 @@ end_per_testcase(TestCase, Config) when TestCase == server_password_option; TestCase == server_userpassword_option; TestCase == server_pwdfun_option; TestCase == server_pwdfun_4_option -> - UserDir = filename:join(?config(priv_dir, Config), nopubkey), + UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), ssh_test_lib:del_dirs(UserDir), end_per_testcase(Config); end_per_testcase(_TestCase, Config) -> @@ -210,10 +224,10 @@ end_per_testcase(_Config) -> %%% validate to server that uses the 'password' option server_password_option(Config) when is_list(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, {user_dir, UserDir}, {password, "morot"}]), @@ -243,10 +257,10 @@ server_password_option(Config) when is_list(Config) -> %%% validate to server that uses the 'password' option server_userpassword_option(Config) when is_list(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, {user_dir, PrivDir}, {user_passwords, [{"vego", "morot"}]}]), @@ -278,10 +292,10 @@ server_userpassword_option(Config) when is_list(Config) -> %%-------------------------------------------------------------------- %%% validate to server that uses the 'pwdfun' option server_pwdfun_option(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), CHKPWD = fun("foo",Pwd) -> Pwd=="bar"; (_,_) -> false end, @@ -316,10 +330,10 @@ server_pwdfun_option(Config) -> %%-------------------------------------------------------------------- %%% validate to server that uses the 'pwdfun/4' option server_pwdfun_4_option(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), PWDFUN = fun("foo",Pwd,{_,_},undefined) -> Pwd=="bar"; ("fie",Pwd,{_,_},undefined) -> {Pwd=="bar",new_state}; ("bandit",_,_,_) -> disconnect; @@ -376,10 +390,10 @@ server_pwdfun_4_option(Config) -> %%-------------------------------------------------------------------- server_pwdfun_4_option_repeat(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), %% Test that the state works Parent = self(), PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true; @@ -471,10 +485,10 @@ user_dir_option(Config) -> %%-------------------------------------------------------------------- %%% validate client that uses the 'ssh_msg_debug_fun' option ssh_msg_debug_fun_option_client(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, {user_dir, UserDir}, @@ -491,7 +505,7 @@ ssh_msg_debug_fun_option_client(Config) -> {user_interaction, false}, {ssh_msg_debug_fun,DbgFun}]), %% Beware, implementation knowledge: - gen_fsm:send_all_state_event(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), + gen_statem:cast(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), receive {msg_dbg,X={ConnectionRef,false,<<"Hello">>,<<>>}} -> ct:log("Got expected dbg msg ~p",[X]), @@ -511,10 +525,10 @@ ssh_msg_debug_fun_option_client(Config) -> %%-------------------------------------------------------------------- connectfun_disconnectfun_server(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), Ref = make_ref(), @@ -540,19 +554,27 @@ connectfun_disconnectfun_server(Config) -> {disconnect,Ref,R} -> ct:log("Disconnect result: ~p",[R]), ssh:stop_daemon(Pid) - after 2000 -> + after 5000 -> + receive + X -> ct:log("received ~p",[X]) + after 0 -> ok + end, {fail, "No disconnectfun action"} end - after 2000 -> + after 5000 -> + receive + X -> ct:log("received ~p",[X]) + after 0 -> ok + end, {fail, "No connectfun action"} end. %%-------------------------------------------------------------------- connectfun_disconnectfun_client(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), Ref = make_ref(), @@ -580,10 +602,10 @@ connectfun_disconnectfun_client(Config) -> %%-------------------------------------------------------------------- %%% validate client that uses the 'ssh_msg_debug_fun' option ssh_msg_debug_fun_option_server(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, @@ -604,7 +626,7 @@ ssh_msg_debug_fun_option_server(Config) -> receive {connection_pid,Server} -> %% Beware, implementation knowledge: - gen_fsm:send_all_state_event(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), + gen_statem:cast(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), receive {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> ct:log("Got expected dbg msg ~p",[X]), @@ -624,10 +646,10 @@ ssh_msg_debug_fun_option_server(Config) -> %%-------------------------------------------------------------------- disconnectfun_option_server(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, @@ -649,7 +671,7 @@ disconnectfun_option_server(Config) -> ct:log("Server detected disconnect: ~p",[Reason]), ssh:stop_daemon(Pid), ok - after 3000 -> + after 5000 -> receive X -> ct:log("received ~p",[X]) after 0 -> ok @@ -659,10 +681,10 @@ disconnectfun_option_server(Config) -> %%-------------------------------------------------------------------- disconnectfun_option_client(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, @@ -693,10 +715,10 @@ disconnectfun_option_client(Config) -> %%-------------------------------------------------------------------- unexpectedfun_option_server(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, @@ -736,10 +758,10 @@ unexpectedfun_option_server(Config) -> %%-------------------------------------------------------------------- unexpectedfun_option_client(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), + SysDir = proplists:get_value(data_dir, Config), Parent = self(), UnexpFun = fun(Msg,Peer) -> @@ -774,6 +796,106 @@ unexpectedfun_option_client(Config) -> end. %%-------------------------------------------------------------------- +hostkey_fingerprint_check(Config) -> + do_hostkey_fingerprint_check(Config, old). + +hostkey_fingerprint_check_md5(Config) -> + do_hostkey_fingerprint_check(Config, md5). + +hostkey_fingerprint_check_sha(Config) -> + do_hostkey_fingerprint_check(Config, sha). + +hostkey_fingerprint_check_sha256(Config) -> + do_hostkey_fingerprint_check(Config, sha256). + +hostkey_fingerprint_check_sha384(Config) -> + do_hostkey_fingerprint_check(Config, sha384). + +hostkey_fingerprint_check_sha512(Config) -> + do_hostkey_fingerprint_check(Config, sha512). + +hostkey_fingerprint_check_list(Config) -> + do_hostkey_fingerprint_check(Config, [sha,md5,sha256]). + +%%%---- +do_hostkey_fingerprint_check(Config, HashAlg) -> + case supported_hash(HashAlg) of + true -> + really_do_hostkey_fingerprint_check(Config, HashAlg); + false -> + {skip,{unsupported_hash,HashAlg}} + end. + +supported_hash(old) -> true; +supported_hash(HashAlg) -> + Hs = if is_atom(HashAlg) -> [HashAlg]; + is_list(HashAlg) -> HashAlg + end, + [] == (Hs -- proplists:get_value(hashs, crypto:supports(), [])). + + +really_do_hostkey_fingerprint_check(Config, HashAlg) -> + PrivDir = proplists:get_value(priv_dir, Config), + UserDirServer = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDirServer), + SysDir = proplists:get_value(data_dir, Config), + + UserDirClient = + ssh_test_lib:create_random_dir(Config), % Ensure no 'known_hosts' disturbs + + %% All host key fingerprints. Trust that public_key has checked the ssh_hostkey_fingerprint + %% function since that function is used by the ssh client... + FPs0 = [case HashAlg of + old -> public_key:ssh_hostkey_fingerprint(Key); + _ -> public_key:ssh_hostkey_fingerprint(HashAlg, Key) + end + || FileCandidate <- begin + {ok,KeyFileCands} = file:list_dir(SysDir), + KeyFileCands + end, + nomatch =/= re:run(FileCandidate, ".*\\.pub", []), + {Key,_Cmnts} <- begin + {ok,Bin} = file:read_file(filename:join(SysDir, FileCandidate)), + try public_key:ssh_decode(Bin, public_key) + catch + _:_ -> [] + end + end], + FPs = if is_atom(HashAlg) -> FPs0; + is_list(HashAlg) -> lists:concat(FPs0) + end, + ct:log("Fingerprints(~p) = ~p",[HashAlg,FPs]), + + %% Start daemon with the public keys that we got fingerprints from + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDirServer}, + {password, "morot"}]), + + FP_check_fun = fun(PeerName, FP) -> + ct:pal("PeerName = ~p, FP = ~p",[PeerName,FP]), + HostCheck = (Host == PeerName), + FPCheck = + if is_atom(HashAlg) -> lists:member(FP, FPs); + is_list(HashAlg) -> lists:all(fun(FP1) -> lists:member(FP1,FPs) end, + FP) + end, + ct:log("check ~p == ~p (~p) and ~n~p~n in ~p (~p)~n", + [PeerName,Host,HostCheck,FP,FPs,FPCheck]), + HostCheck and FPCheck + end, + + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, + case HashAlg of + old -> FP_check_fun; + _ -> {HashAlg, FP_check_fun} + end}, + {user, "foo"}, + {password, "morot"}, + {user_dir, UserDirClient}, + {user_interaction, false}]), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- %%% Test connect_timeout option in ssh:connect/4 ssh_connect_timeout(_Config) -> ConnTimeout = 2000, @@ -859,8 +981,8 @@ ms_passed(T0) -> %%-------------------------------------------------------------------- ssh_daemon_minimal_remote_max_packet_size_option(Config) -> - SystemDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), + SystemDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth file:make_dir(UserDir), @@ -957,8 +1079,8 @@ ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false ssh_connect_negtimeout(Config, Parallel) -> process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), + SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), + UserDir = proplists:get_value(priv_dir, Config), NegTimeOut = 2000, % ms ct:log("Parallel: ~p",[Parallel]), @@ -974,7 +1096,14 @@ ssh_connect_negtimeout(Config, Parallel) -> ct:sleep(round(Factor * NegTimeOut)), case inet:sockname(Socket) of - {ok,_} -> ct:fail("Socket not closed"); + {ok,_} -> + %% Give it another chance... + ct:log("Sleep more...",[]), + ct:sleep(round(Factor * NegTimeOut)), + case inet:sockname(Socket) of + {ok,_} -> ct:fail("Socket not closed"); + {error,_} -> ok + end; {error,_} -> ok end. @@ -990,9 +1119,9 @@ ssh_connect_nonegtimeout_connected_sequential(Config) -> ssh_connect_nonegtimeout_connected(Config, Parallel) -> process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), - NegTimeOut = 20000, % ms + SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), + UserDir = proplists:get_value(priv_dir, Config), + NegTimeOut = 2000, % ms ct:log("Parallel: ~p",[Parallel]), {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, @@ -1003,7 +1132,7 @@ ssh_connect_nonegtimeout_connected(Config, Parallel) -> ct:sleep(500), IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir), + Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]), receive Error = {'EXIT', _, _} -> ct:log("~p",[Error]), @@ -1067,7 +1196,7 @@ connect_fun(ssh__connect, Config) -> fun(Host,Port) -> ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, ?config(priv_dir,Config)}, + {user_dir, proplists:get_value(priv_dir,Config)}, {user_interaction, false}, {user, "carni"}, {password, "meat"} @@ -1092,8 +1221,8 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> ct:log("Connect(~p,~p) -> ~p",[Host,Port,R]), R end, - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), + SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), + UserDir = proplists:get_value(priv_dir, Config), MaxSessions = 5, {Pid, Host, Port} = ssh_test_lib:daemon([ {system_dir, SystemDir}, @@ -1123,21 +1252,7 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> %% This is expected %% Now stop one connection and try to open one more ok = ssh:close(hd(Connections)), - receive after 250 -> ok end, % sleep so the supervisor has time to count down. Not nice... - try Connect(Host,Port) - of - _ConnectionRef1 -> - %% Step 3 ok: could set up one more connection after killing one - %% Thats good. - ssh:stop_daemon(Pid), - ok - catch - error:{badmatch,{error,"Connection closed"}} -> - %% Bad indeed. Could not set up one more connection even after killing - %% one existing. Very bad. - ssh:stop_daemon(Pid), - {fail,"Does not decrease # active sessions"} - end + try_to_connect(Connect, Host, Port, Pid) end catch error:{badmatch,{error,"Connection closed"}} -> @@ -1145,6 +1260,35 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> {fail,"Too few connections accepted"} end. + +try_to_connect(Connect, Host, Port, Pid) -> + {ok,Tref} = timer:send_after(3000, timeout_no_connection), % give the supervisors some time... + try_to_connect(Connect, Host, Port, Pid, Tref, 1). % will take max 3300 ms after 11 tries + +try_to_connect(Connect, Host, Port, Pid, Tref, N) -> + try Connect(Host,Port) + of + _ConnectionRef1 -> + %% Step 3 ok: could set up one more connection after killing one + %% Thats good. + timer:cancel(Tref), + ssh:stop_daemon(Pid), + receive % flush. + timeout_no_connection -> ok + after 0 -> ok + end + catch + error:{badmatch,{error,"Connection closed"}} -> + %% Could not set up one more connection. Try again until timeout. + receive + timeout_no_connection -> + ssh:stop_daemon(Pid), + {fail,"Does not decrease # active sessions"} + after N*50 -> % retry after this time + try_to_connect(Connect, Host, Port, Pid, Tref, N+1) + end + end. + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- |