diff options
Diffstat (limited to 'lib/ssh')
32 files changed, 794 insertions, 266 deletions
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 368bb0f552..0c0c947f65 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,23 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.1.1</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + A new option <c>max_channels</c> limits the number of + channels with active server-side subsystems that are + accepted.</p> + <p> + Own Id: OTP-13036</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 293d618eed..2b190c98b6 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -40,18 +40,24 @@ <list type="bulleted"> <item>For application dependencies see <seealso marker="SSH_app"> ssh(6)</seealso> </item> <item>Supported SSH version is 2.0.</item> - <item>Supported public key algorithms: ssh-rsa and ssh-dss.</item> - <item>Supported MAC algorithms: hmac-sha2-512, hmac-sha2-256 and hmac-sha1.</item> - <item>Supported encryption algorithms: aes128-ctr, aes128-cb and 3des-cbc.</item> - <item>Supported key exchange algorithms: ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, diffie-hellman-group-exchange-sha1 and diffie-hellman-group-exchange-sha256.</item> - <item>Supported compression algorithms: none, zlib</item> + <item>Supported public key algorithms: ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa and ssh-dss.</item> + <item>Supported MAC algorithms: hmac-sha2-256, hmac-sha2-512 and hmac-sha1.</item> + <item>Supported encryption algorithms: aes256-ctr, aes192-ctr, aes128-ctr, aes128-cb and 3des-cbc.</item> + <item>Supported key exchange algorithms: ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha1, diffie-hellman-group-exchange-sha1, diffie-hellman-group-exchange-sha256 and diffie-hellman-group1-sha1</item> + <item>Supported compression algorithms: none, [email protected] and zlib</item> <item>Supports unicode filenames if the emulator and the underlaying OS support it. See section DESCRIPTION in the <seealso marker="kernel:file">file</seealso> manual page in <c>kernel</c> for information about this subject.</item> <item>Supports unicode in shell and CLI.</item> </list> - + <p>The actual set of algorithms can vary depending on which OpenSSL crypto library that is installed on the machine. + For the list on a particular installation, use the command <seealso marker="#default_algorithms/0">default_algorithms/0</seealso>. + The user may override the default algorithm configuration both on the server side and the client side. + See the option preferred_algorithms in the <seealso marker="#daemon/1">daemon</seealso> and + <seealso marker="#connect/3">connect</seealso> functions. +</p> + </section> <section> @@ -501,6 +507,15 @@ kex is implicit but public_key is set explicitly.</p> </p> </item> + <tag><c><![CDATA[{max_channels, pos_integer()}]]></c></tag> + <item> + <p>The maximum number of channels with active remote subsystem that are accepted for + each connection to this daemon</p> + <p>By default, this option is not set. This means that the number is not limited. + </p> + </item> + + <tag><c><![CDATA[{parallel_login, boolean()}]]></c></tag> <item> <p>If set to false (the default value), only one login is handled at a time. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 132de71aed..b6ee29efbb 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -117,9 +117,9 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(integer()) -> {ok, pid()}. --spec daemon(integer(), proplists:proplist()) -> {ok, pid()}. --spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()}. +-spec daemon(integer()) -> {ok, pid()} | {error, term()}. +-spec daemon(integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. %% Description: Starts a server listening for SSH connections %% on the given port. @@ -385,6 +385,8 @@ handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{max_sessions, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{max_channels, _} = Opt|Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{negotiation_timeout, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{parallel_login, _} = Opt|Rest], SocketOptions, SshOptions) -> @@ -443,6 +445,8 @@ handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value Opt; handle_ssh_option({max_sessions, Value} = Opt) when is_integer(Value), Value>0 -> Opt; +handle_ssh_option({max_channels, Value} = Opt) when is_integer(Value), Value>0 -> + Opt; handle_ssh_option({negotiation_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> Opt; handle_ssh_option({parallel_login, Value} = Opt) when Value==true ; Value==false -> diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 726f52132f..0c16e19701 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -31,8 +31,7 @@ -export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, - handle_userauth_info_request/3, handle_userauth_info_response/2, - default_public_key_algorithms/0 + handle_userauth_info_request/3, handle_userauth_info_response/2 ]). %%-------------------------------------------------------------------- @@ -42,27 +41,29 @@ publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = sha, %% Maybe option?! KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:user_key(Alg, Opts) of {ok, Key} -> StrAlgo = algorithm_string(Alg), - PubKeyBlob = encode_public_key(Key), - SigData = build_sig_data(SessionId, - User, Service, PubKeyBlob, StrAlgo), - Sig = ssh_transport:sign(SigData, Hash, Key), - SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "publickey", - data = [?TRUE, - ?string(StrAlgo), - ?binary(PubKeyBlob), - ?binary(SigBlob)]}, - Ssh); + case encode_public_key(StrAlgo, Key) of + not_ok -> + not_ok; + PubKeyBlob -> + SigData = build_sig_data(SessionId, + User, Service, PubKeyBlob, StrAlgo), + Sig = ssh_transport:sign(SigData, Hash, Key), + SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "publickey", + data = [?TRUE, + ?string(StrAlgo), + ?binary(PubKeyBlob), + ?binary(SigBlob)]}, + Ssh) + end; _Error -> not_ok end. @@ -121,7 +122,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> Algs = proplists:get_value(public_key, proplists:get_value(preferred_algorithms, Opts, []), - default_public_key_algorithms()), + ssh_transport:default_algorithms(public_key)), Prefs = method_preference(Algs), ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, @@ -355,8 +356,6 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, language = "en"}). -default_public_key_algorithms() -> ?PREFERRED_PK_ALGS. - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -431,10 +430,13 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) -> ?binary(KeyBlob)], list_to_binary(Sig). -algorithm_string('ssh-rsa') -> - "ssh-rsa"; -algorithm_string('ssh-dss') -> - "ssh-dss". +algorithm_string('ssh-rsa') -> "ssh-rsa"; +algorithm_string('ssh-dss') -> "ssh-dss"; +algorithm_string('ecdsa-sha2-nistp256') -> "ecdsa-sha2-nistp256"; +algorithm_string('ecdsa-sha2-nistp384') -> "ecdsa-sha2-nistp384"; +algorithm_string('ecdsa-sha2-nistp521') -> "ecdsa-sha2-nistp521". + + decode_keyboard_interactive_prompts(_NumPrompts, Data) -> ssh_message:decode_keyboard_interactive_prompts(Data, []). @@ -497,11 +499,35 @@ decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, ?UINT32(Len4), Y:Len4/big-signed-integer-unit:8>> , "ssh-dss") -> {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; - +decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, + ?UINT32(Len1), Id:Len1/binary, %% Id = <<"nistp256">> for example + ?UINT32(Len2), Blob:Len2/binary>>, + Curve) -> + Id = + case Curve of + "ecdsa-sha2-nistp256" -> <<"nistp256">>; + "ecdsa-sha2-nistp384" -> <<"nistp384">>; + "ecdsa-sha2-nistp521" -> <<"nistp521">> + end, + {ok, {#'ECPoint'{point=Blob}, Id}}; decode_public_key_v2(_, _) -> {error, bad_format}. -encode_public_key(#'RSAPrivateKey'{publicExponent = E, modulus = N}) -> +encode_public_key("ssh-rsa", #'RSAPrivateKey'{publicExponent = E, modulus = N}) -> ssh_bits:encode(["ssh-rsa",E,N], [string,mpint,mpint]); -encode_public_key(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) -> - ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]). +encode_public_key("ssh-dss", #'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) -> + ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]); +encode_public_key("ecdsa-sha2-"++Curve, #'ECPrivateKey'{parameters = Params, + publicKey = Pub}) -> + Id = ecdsa_id(Params), + if + Id =/= Curve -> + not_ok; + true -> + ssh_bits:encode(["ecdsa-sha2-"++Id, Id, Pub], + [string, string, binary]) + end. + +ecdsa_id({namedCurve,?'secp256r1'}) -> "nistp256"; +ecdsa_id({namedCurve,?'secp384r1'}) -> "nistp384"; +ecdsa_id({namedCurve,?'secp521r1'}) -> "nistp521". diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl index 71f222f6d7..5197a42fa4 100644 --- a/lib/ssh/src/ssh_auth.hrl +++ b/lib/ssh/src/ssh_auth.hrl @@ -24,8 +24,6 @@ -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(PREFERRED_PK_ALGS, ['ssh-rsa','ssh-dss']). - -define(SSH_MSG_USERAUTH_REQUEST, 50). -define(SSH_MSG_USERAUTH_FAILURE, 51). -define(SSH_MSG_USERAUTH_SUCCESS, 52). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 64d2113125..266c64fd4f 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -935,14 +935,27 @@ encode_ip(Addr) when is_list(Addr) -> end end. -start_channel(Cb, Id, Args, SubSysSup) -> - start_channel(Cb, Id, Args, SubSysSup, undefined). +start_channel(Cb, Id, Args, SubSysSup, Opts) -> + start_channel(Cb, Id, Args, SubSysSup, undefined, Opts). -start_channel(Cb, Id, Args, SubSysSup, Exec) -> +start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ChildSpec = child_spec(Cb, Id, Args, Exec), ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), + assert_limit_num_channels_not_exceeded(ChannelSup, Opts), ssh_channel_sup:start_child(ChannelSup, ChildSpec). +assert_limit_num_channels_not_exceeded(ChannelSup, Opts) -> + MaxNumChannels = proplists:get_value(max_channels, Opts, infinity), + NumChannels = length([x || {_,_,worker,[ssh_channel]} <- + supervisor:which_children(ChannelSup)]), + if + %% Note that NumChannels is BEFORE starting a new one + NumChannels < MaxNumChannels -> + ok; + true -> + throw(max_num_channels_exceeded) + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -998,9 +1011,11 @@ child_spec(Callback, Id, Args, Exec) -> start_cli(#connection{cli_spec = no_cli}, _) -> {error, cli_disabled}; -start_cli(#connection{cli_spec = {CbModule, Args}, exec = Exec, +start_cli(#connection{options = Options, + cli_spec = {CbModule, Args}, + exec = Exec, sub_system_supervisor = SubSysSup}, ChannelId) -> - start_channel(CbModule, ChannelId, Args, SubSysSup, Exec). + start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options). start_subsytem(BinName, #connection{options = Options, sub_system_supervisor = SubSysSup}, @@ -1008,7 +1023,7 @@ start_subsytem(BinName, #connection{options = Options, Name = binary_to_list(BinName), case check_subsystem(Name, Options) of {Callback, Opts} when is_atom(Callback), Callback =/= none -> - start_channel(Callback, ChannelId, Opts, SubSysSup); + start_channel(Callback, ChannelId, Opts, SubSysSup, Options); {Other, _} when Other =/= none -> {error, legacy_option_not_supported} end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 646f787874..7fb86c1108 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -530,7 +530,7 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection", Pid ! ssh_connected, connected_fun(User, Address, Method, Opts), {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + next_packet(State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}})}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Address, Reason, Opts), send_msg(Reply, State), @@ -622,7 +622,7 @@ userauth_keyboard_interactive(#ssh_msg_userauth_info_response{} = Msg, Pid ! ssh_connected, connected_fun(User, Address, "keyboard-interactive", Opts), {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + next_packet(State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}})}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Address, Reason, Opts), send_msg(Reply, State), @@ -1266,9 +1266,9 @@ supported_host_keys(client, _, Options) -> proplists:get_value(preferred_algorithms,Options,[]) ) of undefined -> - ssh_auth:default_public_key_algorithms(); + ssh_transport:default_algorithms(public_key); L -> - L -- (L--ssh_auth:default_public_key_algorithms()) + L -- (L--ssh_transport:default_algorithms(public_key)) end of [] -> @@ -1280,21 +1280,17 @@ supported_host_keys(client, _, Options) -> {stop, {shutdown, Reason}} end; supported_host_keys(server, KeyCb, Options) -> - Algs= [atom_to_list(A) || A <- proplists:get_value(public_key, proplists:get_value(preferred_algorithms,Options,[]), - ssh_auth:default_public_key_algorithms() + ssh_transport:default_algorithms(public_key) ), available_host_key(KeyCb, A, Options) - ], - Algs. - + ]. %% Alg :: atom() available_host_key(KeyCb, Alg, Opts) -> element(1, catch KeyCb:host_key(Alg, Opts)) == ok. - send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) -> Transport:send(Socket, Msg). diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index b98a8a8410..4e6d58cbff 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -52,8 +52,20 @@ host_key(Algorithm, Opts) -> %% so probably we could hardcod Password = ignore, but %% we keep it as an undocumented option for now. Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), - decode(File, Password). - + case decode(File, Password) of + {ok,Key} -> + case {Key,Algorithm} of + {#'RSAPrivateKey'{}, 'ssh-rsa'} -> {ok,Key}; + {#'DSAPrivateKey'{}, 'ssh-dss'} -> {ok,Key}; + {#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}, 'ecdsa-sha2-nistp256'} -> {ok,Key}; + {#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}, 'ecdsa-sha2-nistp384'} -> {ok,Key}; + {#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}, 'ecdsa-sha2-nistp521'} -> {ok,Key}; + _ -> + {error,bad_keytype_in_file} + end; + Other -> + Other + end. is_auth_key(Key, User,Opts) -> case lookup_user_key(Key, User, Opts) of @@ -81,16 +93,15 @@ user_key(Algorithm, Opts) -> %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -file_base_name('ssh-rsa') -> - "ssh_host_rsa_key"; -file_base_name('ssh-dss') -> - "ssh_host_dsa_key"; -file_base_name(_) -> - "ssh_host_key". +file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; +file_base_name('ssh-dss' ) -> "ssh_host_dsa_key"; +file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; +file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; +file_base_name('ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key"; +file_base_name(_ ) -> "ssh_host_key". decode(File, Password) -> - try - {ok, decode_ssh_file(read_ssh_file(File), Password)} + try {ok, decode_ssh_file(read_ssh_file(File), Password)} catch throw:Reason -> {error, Reason}; @@ -215,20 +226,18 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> Error -> Error end. -identity_key_filename('ssh-dss') -> - "id_dsa"; -identity_key_filename('ssh-rsa') -> - "id_rsa". - -identity_pass_phrase("ssh-dss") -> - dsa_pass_phrase; -identity_pass_phrase('ssh-dss') -> - dsa_pass_phrase; -identity_pass_phrase('ssh-rsa') -> - rsa_pass_phrase; -identity_pass_phrase("ssh-rsa") -> - rsa_pass_phrase. - +identity_key_filename('ssh-dss' ) -> "id_dsa"; +identity_key_filename('ssh-rsa' ) -> "id_rsa"; +identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa"; +identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa"; +identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa". + +identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase; +identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase; +identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase; +identity_pass_phrase(P) when is_atom(P) -> + identity_pass_phrase(atom_to_list(P)). + lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) -> case io:get_line(Fd, '') of eof -> @@ -267,6 +276,12 @@ key_match(#'RSAPublicKey'{}, 'ssh-rsa') -> true; key_match({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; +key_match({#'ECPoint'{},<<"nistp256">>}, 'ecdsa-sha2-nistp256') -> + true; +key_match({#'ECPoint'{},<<"nistp384">>}, 'ecdsa-sha2-nistp384') -> + true; +key_match({#'ECPoint'{},<<"nistp521">>}, 'ecdsa-sha2-nistp521') -> + true; key_match(_, _) -> false. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index cb1dcb67c5..cfa11903fb 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -564,9 +564,11 @@ decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) -> decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> Signature. + decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) -> decode_host_key(Alg, Rest). + decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/big-signed-integer-unit:8, ?UINT32(Len1), N:Len1/big-signed-integer-unit:8>>) -> #'RSAPublicKey'{publicExponent = E, @@ -579,19 +581,43 @@ decode_host_key(<<"ssh-dss">>, ?UINT32(Len3), Y:Len3/big-signed-integer-unit:8>>) -> {Y, #'Dss-Parms'{p = P, q = Q, - g = G}}. + g = G}}; + +decode_host_key(<<"ecdsa-sha2-",Id/binary>>, + <<?UINT32(Len0), Id:Len0/binary, %% Id = <<"nistp256">> for example + ?UINT32(Len1), Blob:Len1/binary>>) -> + {#'ECPoint'{point=Blob}, Id}. + encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); encode_host_key({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) -> ssh_bits:encode(["ssh-dss", P, Q, G, Y], [string, mpint, mpint, mpint, mpint]); +encode_host_key({#'ECPoint'{point = Q}, Id}) -> + ssh_bits:encode([<<"ecdsa-sha2-",Id/binary>>,Id,Q], [binary,binary,binary]); + encode_host_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); encode_host_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> ssh_bits:encode(["ssh-dss", P, Q, G, Y], - [string, mpint, mpint, mpint, mpint]). + [string, mpint, mpint, mpint, mpint]); +encode_host_key(#'ECPrivateKey'{parameters = Params, %{namedCurve,{1,2,840,10045,3,1,7}}, + publicKey = Pub}) -> + Id = ecdsa_id(Params), + ssh_bits:encode(["ecdsa-sha2-"++Id, Id, Pub], + [string, string, binary]). + + encode_sign(#'RSAPrivateKey'{}, Signature) -> ssh_bits:encode(["ssh-rsa", Signature],[string, binary]); encode_sign(#'DSAPrivateKey'{}, Signature) -> - ssh_bits:encode(["ssh-dss", Signature],[string, binary]). + ssh_bits:encode(["ssh-dss", Signature],[string, binary]); +encode_sign(#'ECPrivateKey'{parameters = Params}, Signature) -> + Id = "ecdsa-sha2-" ++ ecdsa_id(Params), + ssh_bits:encode([Id, Signature],[string, binary]). + + +ecdsa_id({namedCurve,?'secp256r1'}) -> "nistp256"; +ecdsa_id({namedCurve,?'secp384r1'}) -> "nistp384"; +ecdsa_id({namedCurve,?'secp521r1'}) -> "nistp521". diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 2b6f0a3cdc..a6438e69d4 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -65,9 +65,8 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. -default_algorithms(compression) -> - %% Do not announce '[email protected]' because there seem to be problems - supported_algorithms(compression, same(['[email protected]'])); +default_algorithms(kex) -> + supported_algorithms(kex, []); %% Just to have a call to supported_algorithms/2 default_algorithms(Alg) -> supported_algorithms(Alg). @@ -79,18 +78,27 @@ supported_algorithms(kex) -> [ {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, + {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, + {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, - {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, - {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} + {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} ]); supported_algorithms(public_key) -> - ssh_auth:default_public_key_algorithms(); + select_crypto_supported( + [{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, + {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, + {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, + {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} + ]); + supported_algorithms(cipher) -> same( select_crypto_supported( - [{'aes128-ctr', [{ciphers,aes_ctr}]}, + [{'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, + {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, + {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, {'aes128-cbc', [{ciphers,aes_cbc128}]}, {'3des-cbc', [{ciphers,des3_cbc}]} ] @@ -98,14 +106,16 @@ supported_algorithms(cipher) -> supported_algorithms(mac) -> same( select_crypto_supported( - [{'hmac-sha2-512', [{hashs,sha512}]}, - {'hmac-sha2-256', [{hashs,sha256}]}, + [{'hmac-sha2-256', [{hashs,sha256}]}, + {'hmac-sha2-512', [{hashs,sha512}]}, {'hmac-sha1', [{hashs,sha}]} ] )); supported_algorithms(compression) -> - same(['none','zlib','[email protected]']). - + same(['none', + '[email protected]', + 'zlib' + ]). supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) -> [{client2server,As1},{server2client,As2}] = supported_algorithms(Key), @@ -124,10 +134,25 @@ crypto_supported_curves() -> end. crypto_supported(Conditions, Supported) -> - lists:all( fun({Tag,CryptoName}) -> - lists:member(CryptoName, proplists:get_value(Tag,Supported,[])) + lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) -> + crypto_name_supported(Tag,CryptoName,Supported); + ({Tag,{Name=aes_ctr,Len}}) when is_integer(Len) -> + crypto_name_supported(Tag,Name,Supported) andalso + ctr_len_supported(Name,Len) end, Conditions). +crypto_name_supported(Tag, CryptoName, Supported) -> + lists:member(CryptoName, proplists:get_value(Tag,Supported,[])). + +ctr_len_supported(Name, Len) -> + try + crypto:stream_encrypt(crypto:stream_init(Name, <<0:Len>>, <<0:128>>), <<"">>) + of + {_,X} -> is_binary(X) + catch + _:_ -> false + end. + same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. @@ -319,11 +344,12 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; -key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group-exchange-sha1' ; - Kex == 'diffie-hellman-group-exchange-sha256' -> - Min = ?DEFAULT_DH_GROUP_MIN, - NBits = ?DEFAULT_DH_GROUP_NBITS, - Max = ?DEFAULT_DH_GROUP_MAX, +key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; + Kex == 'diffie-hellman-group-exchange-sha256' -> + {Min,NBits,Max} = + proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, + ?DEFAULT_DH_GROUP_NBITS, + ?DEFAULT_DH_GROUP_MAX}), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, n = NBits, @@ -623,33 +649,40 @@ get_host_key(SSH) -> #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH, case Mod:host_key(ALG#alg.hkey, Opts) of - {ok, #'RSAPrivateKey'{} = Key} -> - Key; - {ok, #'DSAPrivateKey'{} = Key} -> - Key; + {ok, #'RSAPrivateKey'{} = Key} -> Key; + {ok, #'DSAPrivateKey'{} = Key} -> Key; + {ok, #'ECPrivateKey'{} = Key} -> Key; Result -> exit({error, {Result, unsupported_key_type}}) end. -sign_host_key(_Ssh, #'RSAPrivateKey'{} = Private, H) -> - Hash = sha, - _Signature = sign(H, Hash, Private); -sign_host_key(_Ssh, #'DSAPrivateKey'{} = Private, H) -> - Hash = sha, - _RawSignature = sign(H, Hash, Private). +sign_host_key(_Ssh, PrivateKey, H) -> + sign(H, sign_host_key_sha(PrivateKey), PrivateKey). + +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}) -> sha256; +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}) -> sha384; +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}) -> sha512; +sign_host_key_sha(#'RSAPrivateKey'{}) -> sha; +sign_host_key_sha(#'DSAPrivateKey'{}) -> sha. + verify_host_key(SSH, PublicKey, Digest, Signature) -> - case verify(Digest, sha, Signature, PublicKey) of + case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of false -> {error, bad_signature}; true -> known_host_key(SSH, PublicKey, public_algo(PublicKey)) end. -public_algo(#'RSAPublicKey'{}) -> - 'ssh-rsa'; -public_algo({_, #'Dss-Parms'{}}) -> - 'ssh-dss'. +host_key_sha(#'RSAPublicKey'{}) -> sha; +host_key_sha({_, #'Dss-Parms'{}}) -> sha; +host_key_sha({#'ECPoint'{},Id}) -> sha(list_to_atom(binary_to_list(Id))). + + +public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; +public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; +public_algo({#'ECPoint'{},Id}) -> list_to_atom("ecdsa-sha2-" ++ binary_to_list(Id)). + accepted_host(Ssh, PeerName, Opts) -> case proplists:get_value(silently_accept_hosts, Opts, false) of @@ -889,6 +922,10 @@ sign(SigData, Hash, #'DSAPrivateKey'{} = Key) -> DerSignature = public_key:sign(SigData, Hash, Key), #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>; +sign(SigData, Hash, Key = #'ECPrivateKey'{}) -> + DerEncodedSign = public_key:sign(SigData, Hash, Key), + #'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign), + ssh_bits:encode([R,S], [mpint,mpint]); sign(SigData, Hash, Key) -> public_key:sign(SigData, Hash, Key). @@ -896,55 +933,23 @@ verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = Sig, Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), public_key:verify(PlainText, Hash, Signature, Key); +verify(PlainText, Hash, Sig, {ECPoint=#'ECPoint'{}, Param}) -> + C = case Param of + <<"nistp256">> -> {namedCurve, ?'secp256r1'}; + <<"nistp384">> -> {namedCurve, ?'secp384r1'}; + <<"nistp521">> -> {namedCurve, ?'secp521r1'} + end, + <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8, + ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> = Sig, + Sval = #'ECDSA-Sig-Value'{r=R, s=S}, + DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), + public_key:verify(PlainText, Hash, DerEncodedSig, {ECPoint,C}); verify(PlainText, Hash, Sig, Key) -> public_key:verify(PlainText, Hash, Sig, Key). -%% public key algorithms -%% -%% ssh-dss REQUIRED sign Raw DSS Key -%% ssh-rsa RECOMMENDED sign Raw RSA Key -%% x509v3-sign-rsa OPTIONAL sign X.509 certificates (RSA key) -%% x509v3-sign-dss OPTIONAL sign X.509 certificates (DSS key) -%% spki-sign-rsa OPTIONAL sign SPKI certificates (RSA key) -%% spki-sign-dss OPTIONAL sign SPKI certificates (DSS key) -%% pgp-sign-rsa OPTIONAL sign OpenPGP certificates (RSA key) -%% pgp-sign-dss OPTIONAL sign OpenPGP certificates (DSS key) -%% - -%% key exchange -%% -%% diffie-hellman-group1-sha1 REQUIRED -%% diffie-hellman-group14-sha1 REQUIRED -%% -%% - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Encryption -%% -%% chiphers %% -%% 3des-cbc REQUIRED -%% three-key 3DES in CBC mode -%% blowfish-cbc OPTIONAL Blowfish in CBC mode -%% twofish256-cbc OPTIONAL Twofish in CBC mode, -%% with 256-bit key -%% twofish-cbc OPTIONAL alias for "twofish256-cbc" (this -%% is being retained for -%% historical reasons) -%% twofish192-cbc OPTIONAL Twofish with 192-bit key -%% twofish128-cbc OPTIONAL Twofish with 128-bit key -%% aes256-cbc OPTIONAL AES in CBC mode, -%% with 256-bit key -%% aes192-cbc OPTIONAL AES with 192-bit key -%% aes128-cbc RECOMMENDED AES with 128-bit key -%% serpent256-cbc OPTIONAL Serpent in CBC mode, with -%% 256-bit key -%% serpent192-cbc OPTIONAL Serpent with 192-bit key -%% serpent128-cbc OPTIONAL Serpent with 128-bit key -%% arcfour OPTIONAL the ARCFOUR stream cipher -%% idea-cbc OPTIONAL IDEA in CBC mode -%% cast128-cbc OPTIONAL CAST-128 in CBC mode -%% none OPTIONAL no encryption; NOT RECOMMENDED +%% Encryption %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -975,18 +980,46 @@ encrypt_init(#ssh{encrypt = 'aes128-cbc', role = server} = Ssh) -> encrypt_block_size = 16, encrypt_ctx = IV}}; encrypt_init(#ssh{encrypt = 'aes128-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "A", 128), + IV = hash(Ssh, "A", 128), <<K:16/binary>> = hash(Ssh, "C", 128), State = crypto:stream_init(aes_ctr, K, IV), {ok, Ssh#ssh{encrypt_keys = K, encrypt_block_size = 16, encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes192-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:24/binary>> = hash(Ssh, "C", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, + encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes256-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:32/binary>> = hash(Ssh, "C", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, + encrypt_ctx = State}}; encrypt_init(#ssh{encrypt = 'aes128-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "B", 128), + IV = hash(Ssh, "B", 128), <<K:16/binary>> = hash(Ssh, "D", 128), State = crypto:stream_init(aes_ctr, K, IV), {ok, Ssh#ssh{encrypt_keys = K, encrypt_block_size = 16, + encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes192-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:24/binary>> = hash(Ssh, "D", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, + encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes256-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:32/binary>> = hash(Ssh, "D", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, encrypt_ctx = State}}. encrypt_final(Ssh) -> @@ -1013,6 +1046,14 @@ encrypt(#ssh{encrypt = 'aes128-cbc', encrypt(#ssh{encrypt = 'aes128-ctr', encrypt_ctx = State0} = Ssh, Data) -> {State, Enc} = crypto:stream_encrypt(State0,Data), + {Ssh#ssh{encrypt_ctx = State}, Enc}; +encrypt(#ssh{encrypt = 'aes192-ctr', + encrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_encrypt(State0,Data), + {Ssh#ssh{encrypt_ctx = State}, Enc}; +encrypt(#ssh{encrypt = 'aes256-ctr', + encrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_encrypt(State0,Data), {Ssh#ssh{encrypt_ctx = State}, Enc}. @@ -1053,12 +1094,40 @@ decrypt_init(#ssh{decrypt = 'aes128-ctr', role = client} = Ssh) -> {ok, Ssh#ssh{decrypt_keys = K, decrypt_block_size = 16, decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes192-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:24/binary>> = hash(Ssh, "D", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, + decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes256-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:32/binary>> = hash(Ssh, "D", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, + decrypt_ctx = State}}; decrypt_init(#ssh{decrypt = 'aes128-ctr', role = server} = Ssh) -> IV = hash(Ssh, "A", 128), <<K:16/binary>> = hash(Ssh, "C", 128), State = crypto:stream_init(aes_ctr, K, IV), {ok, Ssh#ssh{decrypt_keys = K, decrypt_block_size = 16, + decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes192-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:24/binary>> = hash(Ssh, "C", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, + decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes256-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:32/binary>> = hash(Ssh, "C", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, decrypt_ctx = State}}. @@ -1084,6 +1153,14 @@ decrypt(#ssh{decrypt = 'aes128-cbc', decrypt_keys = Key, decrypt(#ssh{decrypt = 'aes128-ctr', decrypt_ctx = State0} = Ssh, Data) -> {State, Enc} = crypto:stream_decrypt(State0,Data), + {Ssh#ssh{decrypt_ctx = State}, Enc}; +decrypt(#ssh{decrypt = 'aes192-ctr', + decrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_decrypt(State0,Data), + {Ssh#ssh{decrypt_ctx = State}, Enc}; +decrypt(#ssh{decrypt = 'aes256-ctr', + decrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_decrypt(State0,Data), {Ssh#ssh{decrypt_ctx = State}, Enc}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1168,17 +1245,8 @@ decompress(#ssh{decompress = '[email protected]', decompress_ctx = Context, authe {Ssh, list_to_binary(Decompressed)}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% MAC calculation %% -%% hmac-sha1 REQUIRED HMAC-SHA1 (digest length = key -%% length = 20) -%% hmac-sha1-96 RECOMMENDED first 96 bits of HMAC-SHA1 (digest -%% length = 12, key length = 20) -%% hmac-md5 OPTIONAL HMAC-MD5 (digest length = key -%% length = 16) -%% hmac-md5-96 OPTIONAL first 96 bits of HMAC-MD5 (digest -%% length = 12, key length = 16) -%% none OPTIONAL no MAC; NOT RECOMMENDED +%% MAC calculation %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1350,7 +1418,7 @@ dh_gex_group(Min, N, Max, undefined) -> dh_gex_group(Min, N, Max, dh_gex_default_groups()); dh_gex_group(Min, N, Max, Groups) -> %% First try to find an exact match. If not an exact match, select the largest possible. - {_,Group} = + {_Size,Group} = lists:foldl( fun(_, {I,G}) when I==N -> %% If we have an exact match already: use that one diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 96ab1bb668..17a0daebe3 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -33,7 +33,7 @@ -define(MAX_NUM_ALGORITHMS, 200). -define(DEFAULT_DH_GROUP_MIN, 1024). --define(DEFAULT_DH_GROUP_NBITS, 6144). +-define(DEFAULT_DH_GROUP_NBITS, 2048). -define(DEFAULT_DH_GROUP_MAX, 8192). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl index e67fa2469f..9f388de2a7 100644 --- a/lib/ssh/test/ssh_algorithms_SUITE.erl +++ b/lib/ssh/test/ssh_algorithms_SUITE.erl @@ -23,6 +23,7 @@ -module(ssh_algorithms_SUITE). -include_lib("common_test/include/ct.hrl"). +-include_lib("ssh/src/ssh_transport.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -72,11 +73,19 @@ init_per_suite(Config) -> "OS ssh:~n=======~n~p~n~n~n" "Erl ssh:~n========~n~p~n~n~n" "Installed ssh client:~n=====================~n~p~n~n~n" - "Installed ssh server:~n=====================~n~p~n~n~n", - [os:cmd("ssh -V"), + "Installed ssh server:~n=====================~n~p~n~n~n" + "Misc values:~n============~n" + " -- Default dh group exchange parameters ({min,def,max}): ~p~n" + " -- dh_default_groups: ~p~n" + " -- Max num algorithms: ~p~n" + ,[os:cmd("ssh -V"), ssh:default_algorithms(), ssh_test_lib:default_algorithms(sshc), - ssh_test_lib:default_algorithms(sshd)]), + ssh_test_lib:default_algorithms(sshd), + {?DEFAULT_DH_GROUP_MIN,?DEFAULT_DH_GROUP_NBITS,?DEFAULT_DH_GROUP_MAX}, + [KeyLen || {KeyLen,_} <- ?dh_default_groups], + ?MAX_NUM_ALGORITHMS + ]), ct:log("all() ->~n ~p.~n~ngroups()->~n ~p.~n",[all(),groups()]), catch crypto:stop(), case catch crypto:start() of @@ -162,6 +171,21 @@ simple_exec(Config) -> ssh_test_lib:std_simple_exec(Host, Port, Config). %%-------------------------------------------------------------------- +%% Testing all default groups +simple_exec_group14(Config) -> simple_exec_group(2048, Config). +simple_exec_group15(Config) -> simple_exec_group(3072, Config). +simple_exec_group16(Config) -> simple_exec_group(4096, Config). +simple_exec_group17(Config) -> simple_exec_group(6144, Config). +simple_exec_group18(Config) -> simple_exec_group(8192, Config). + +simple_exec_group(I, Config) -> + Min = I-100, + Max = I+100, + {Host,Port} = ?config(srvr_addr, Config), + ssh_test_lib:std_simple_exec(Host, Port, Config, + [{dh_gex_limits,{Min,I,Max}}]). + +%%-------------------------------------------------------------------- %% Use the ssh client of the OS to connect sshc_simple_exec(Config) -> PrivDir = ?config(priv_dir, Config), @@ -254,6 +278,17 @@ specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos) -> [sshd_simple_exec]; _ -> [] + end ++ + case {Tag,Alg} of + {kex,_} when Alg == 'diffie-hellman-group-exchange-sha1' ; + Alg == 'diffie-hellman-group-exchange-sha256' -> + [simple_exec_group14, + simple_exec_group15, + simple_exec_group16, + simple_exec_group17, + simple_exec_group18]; + _ -> + [] end. supports(Tag, Alg, Algos) -> diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 51431da48e..24d8a4e53c 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -77,6 +77,9 @@ all() -> appup_test, {group, dsa_key}, {group, rsa_key}, + {group, ecdsa_sha2_nistp256_key}, + {group, ecdsa_sha2_nistp384_key}, + {group, ecdsa_sha2_nistp521_key}, {group, dsa_pass_key}, {group, rsa_pass_key}, {group, internal_error}, @@ -89,6 +92,9 @@ all() -> groups() -> [{dsa_key, [], basic_tests()}, {rsa_key, [], basic_tests()}, + {ecdsa_sha2_nistp256_key, [], basic_tests()}, + {ecdsa_sha2_nistp384_key, [], basic_tests()}, + {ecdsa_sha2_nistp521_key, [], basic_tests()}, {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, {internal_error, [], [internal_error]} @@ -129,6 +135,39 @@ init_per_group(rsa_key, Config) -> PrivDir = ?config(priv_dir, Config), ssh_test_lib:setup_rsa(DataDir, PrivDir), Config; +init_per_group(ecdsa_sha2_nistp256_key, Config) -> + case lists:member('ecdsa-sha2-nistp256', + ssh_transport:default_algorithms(public_key)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir), + Config; + false -> + {skip, unsupported_pub_key} + end; +init_per_group(ecdsa_sha2_nistp384_key, Config) -> + case lists:member('ecdsa-sha2-nistp384', + ssh_transport:default_algorithms(public_key)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_ecdsa("384", DataDir, PrivDir), + Config; + false -> + {skip, unsupported_pub_key} + end; +init_per_group(ecdsa_sha2_nistp521_key, Config) -> + case lists:member('ecdsa-sha2-nistp521', + ssh_transport:default_algorithms(public_key)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_ecdsa("521", DataDir, PrivDir), + Config; + false -> + {skip, unsupported_pub_key} + end; init_per_group(rsa_pass_key, Config) -> DataDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -362,30 +401,36 @@ exec(Config) when is_list(Config) -> %%-------------------------------------------------------------------- %%% Test that compression option works exec_compressed(Config) when is_list(Config) -> - process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, - {preferred_algorithms,[{compression, [zlib]}]}, - {failfun, fun ssh_test_lib:failfun/2}]), + case ssh_test_lib:ssh_supports(zlib, compression) of + false -> + {skip, "zlib compression is not supported"}; + + true -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, + {preferred_algorithms,[{compression, [zlib]}]}, + {failfun, fun ssh_test_lib:failfun/2}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user_interaction, false}]), - {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), - success = ssh_connection:exec(ConnectionRef, ChannelId, - "1+1.", infinity), - Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2\n">>}}, - case ssh_test_lib:receive_exec_result(Data) of - expected -> - ok; - Other -> - ct:fail(Other) - end, - ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId), - ssh:stop_daemon(Pid). + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:exec(ConnectionRef, ChannelId, + "1+1.", infinity), + Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2\n">>}}, + case ssh_test_lib:receive_exec_result(Data) of + expected -> + ok; + Other -> + ct:fail(Other) + end, + ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId), + ssh:stop_daemon(Pid) + end. %%-------------------------------------------------------------------- %%% Idle timeout test @@ -428,6 +473,8 @@ shell(Config) when is_list(Config) -> ErlShellStart -> ct:log("Erlang shell start: ~p~n", [ErlShellStart]), do_shell(IO, Shell) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- @@ -456,11 +503,15 @@ cli(Config) when is_list(Config) -> {ssh_cm, ConnectionRef, {data,0,0, <<"\r\nYou are accessing a dummy, type \"q\" to exit\r\n\n">>}} -> ok = ssh_connection:send(ConnectionRef, ChannelId, <<"q">>) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef,{closed, ChannelId}} -> ok + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- @@ -599,7 +650,7 @@ peername_sockname(Config) when is_list(Config) -> host_equal(HostSockSrv, Host), PortSockSrv = Port after 10000 -> - throw(timeout) + ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. host_equal(H1, H2) -> @@ -633,7 +684,7 @@ close(Config) when is_list(Config) -> {ssh_cm, Client,{closed, ChannelId}} -> ok after 5000 -> - ct:fail(timeout) + ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- @@ -708,22 +759,28 @@ shell_unicode_string(Config) -> %%-------------------------------------------------------------------- %%% Test basic connection with openssh_zlib openssh_zlib_basic_test(Config) -> - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), + case ssh_test_lib:ssh_supports(['[email protected]',none], compression) of + {false,L} -> + {skip, io_lib:format("~p compression is not supported",[L])}; - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {preferred_algorithms,[{compression, ['[email protected]']}]}, - {failfun, fun ssh_test_lib:failfun/2}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user_interaction, false}, - {preferred_algorithms,[{compression, ['[email protected]', - none]}]} - ]), - ok = ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). + true -> + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {preferred_algorithms,[{compression, ['[email protected]']}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}, + {preferred_algorithms,[{compression, ['[email protected]', + none]}]} + ]), + ok = ssh:close(ConnectionRef), + ssh:stop_daemon(Pid) + end. %%-------------------------------------------------------------------- ssh_info_print(Config) -> @@ -825,22 +882,32 @@ do_shell(IO, Shell) -> receive Echo0 -> ct:log("Echo: ~p ~n", [Echo0]) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive ?NEWLINE -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive Result0 = <<"2">> -> ct:log("Result: ~p~n", [Result0]) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive ?NEWLINE -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive ErlPrompt1 -> ct:log("Erlang prompt: ~p~n", [ErlPrompt1]) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, exit(Shell, kill). %%Does not seem to work in the testserver! diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256 new file mode 100644 index 0000000000..4b1eb12eaa --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49 +AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br +N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256.pub new file mode 100644 index 0000000000..a0147e60fa --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384 new file mode 100644 index 0000000000..4e8aa40959 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S +PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd +ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF +8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384.pub new file mode 100644 index 0000000000..41e722e545 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521 new file mode 100644 index 0000000000..7196f46e97 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q +9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5 +01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7 +XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf +pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521.pub new file mode 100644 index 0000000000..8f059120bc --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256 new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384 new file mode 100644 index 0000000000..fb1a862ded --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw +mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3 +CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7 +Hneb/99fIYopdMH5NMnk60zGO1uZ2vc= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384.pub new file mode 100644 index 0000000000..428d5fb7d7 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521 new file mode 100644 index 0000000000..3e51ec2ecd --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx +U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq +Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/ +vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5 +ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521.pub new file mode 100644 index 0000000000..017a29f4da --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl index fbcf06290a..f0fdf5c0cc 100644 --- a/lib/ssh/test/ssh_connection_SUITE.erl +++ b/lib/ssh/test/ssh_connection_SUITE.erl @@ -48,7 +48,8 @@ all() -> gracefull_invalid_long_start, gracefull_invalid_long_start_no_nl, stop_listener, - start_subsystem_on_closed_channel + start_subsystem_on_closed_channel, + max_channels_option ]. groups() -> [{openssh, [], payload() ++ ptty()}]. @@ -119,20 +120,28 @@ simple_exec(Config) when is_list(Config) -> receive {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, %% receive close messages receive {ssh_cm, ConnectionRef, {eof, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- @@ -154,20 +163,28 @@ small_cat(Config) when is_list(Config) -> receive {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Data}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, %% receive close messages receive {ssh_cm, ConnectionRef, {eof, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- big_cat() -> @@ -211,11 +228,15 @@ big_cat(Config) when is_list(Config) -> %% receive close messages (eof already consumed) receive {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} -> - ok + ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- @@ -234,14 +255,20 @@ send_after_exit(Config) when is_list(Config) -> receive {ssh_cm, ConnectionRef, {eof, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef, {exit_status, ChannelId0, _ExitStatus}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef,{closed, ChannelId0}} -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, case ssh_connection:send(ConnectionRef, ChannelId0, Data, 2000) of {error, closed} -> ok; @@ -455,6 +482,8 @@ gracefull_invalid_version(Config) when is_list(Config) -> {tcp_closed, S} -> ok end + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. gracefull_invalid_start(Config) when is_list(Config) -> @@ -475,6 +504,8 @@ gracefull_invalid_start(Config) when is_list(Config) -> {tcp_closed, S} -> ok end + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. gracefull_invalid_long_start(Config) when is_list(Config) -> @@ -495,6 +526,8 @@ gracefull_invalid_long_start(Config) when is_list(Config) -> {tcp_closed, S} -> ok end + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. @@ -516,6 +549,8 @@ gracefull_invalid_long_start_no_nl(Config) when is_list(Config) -> {tcp_closed, S} -> ok end + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. stop_listener() -> @@ -606,6 +641,78 @@ start_subsystem_on_closed_channel(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +max_channels_option() -> + [{doc, "Test max_channels option"}]. + +max_channels_option(Config) when is_list(Config) -> + PrivDir = ?config(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), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}, + {max_channels, 3}, + {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]} + ]), + + ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), + + {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), + {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity), + {ok, ChannelId2} = ssh_connection:session_channel(ConnectionRef, infinity), + {ok, ChannelId3} = ssh_connection:session_channel(ConnectionRef, infinity), + {ok, ChannelId4} = ssh_connection:session_channel(ConnectionRef, infinity), + {ok, ChannelId5} = ssh_connection:session_channel(ConnectionRef, infinity), + {ok, _ChannelId6} = ssh_connection:session_channel(ConnectionRef, infinity), + + %%%---- shell + ok = ssh_connection:shell(ConnectionRef,ChannelId0), + receive + {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"Eshell",_/binary>>}} -> + ok + after 5000 -> + ct:fail("CLI Timeout") + end, + + %%%---- subsystem "echo_n" + success = ssh_connection:subsystem(ConnectionRef, ChannelId1, "echo_n", infinity), + + %%%---- exec #1 + success = ssh_connection:exec(ConnectionRef, ChannelId2, "testing1.\n", infinity), + receive + {ssh_cm, ConnectionRef, {data, ChannelId2, 0, <<"testing1",_/binary>>}} -> + ok + after 5000 -> + ct:fail("Exec #1 Timeout") + end, + + %%%---- ptty + success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId3, []), + + %%%---- exec #2 + failure = ssh_connection:exec(ConnectionRef, ChannelId4, "testing2.\n", infinity), + + %%%---- close the shell + ok = ssh_connection:send(ConnectionRef, ChannelId0, "exit().\n", 5000), + + %%%---- exec #3 + success = ssh_connection:exec(ConnectionRef, ChannelId5, "testing3.\n", infinity), + receive + {ssh_cm, ConnectionRef, {data, ChannelId5, 0, <<"testing3",_/binary>>}} -> + ok + after 5000 -> + ct:fail("Exec #3 Timeout") + end, + + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- big_cat_rx(ConnectionRef, ChannelId) -> diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index d64c78da35..cf15ca4253 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -656,6 +656,8 @@ ssh_connect_arg4_timeout(_Config) -> %% Get listening port Port = receive {port,Server,ServerPort} -> ServerPort + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, %% try to connect with a timeout, but "supervise" it @@ -861,6 +863,8 @@ ssh_connect_nonegtimeout_connected(Config, Parallel) -> ct:sleep(round(Factor * NegTimeOut)), one_shell_op(IO, NegTimeOut) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, exit(Shell, kill). @@ -869,13 +873,13 @@ one_shell_op(IO, TimeOut) -> ct:log("One shell op: Waiting for prompter"), receive ErlPrompt0 -> ct:log("Erlang prompt: ~p~n", [ErlPrompt0]) - after TimeOut -> ct:fail("Timeout waiting for promter") + after TimeOut -> ct:fail("Timeout waiting for promter") end, IO ! {input, self(), "2*3*7.\r\n"}, receive Echo0 -> ct:log("Echo: ~p ~n", [Echo0]) - after TimeOut -> ct:fail("Timeout waiting for echo") + after TimeOut -> ct:fail("Timeout waiting for echo") end, receive @@ -888,7 +892,7 @@ one_shell_op(IO, TimeOut) -> receive Result0 -> ct:log("Result: ~p~n", [Result0]) - after TimeOut -> ct:fail("Timeout waiting for result") + after TimeOut -> ct:fail("Timeout waiting for result") end. %%-------------------------------------------------------------------- @@ -1016,9 +1020,13 @@ fake_daemon(_Config) -> {ok,S} = Rsa, receive {tcp, S, Id} -> Parent ! {id,self(),Id} + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end end), %% Get listening host and port receive {sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort} + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl index d8e99799e2..743282ce9c 100644 --- a/lib/ssh/test/ssh_protocol_SUITE.erl +++ b/lib/ssh/test/ssh_protocol_SUITE.erl @@ -91,7 +91,7 @@ init_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ; [] end, start_std_daemon(Config, - [{preferred_algorithms, ssh_transport:supported_algorithms()} + [{preferred_algorithms, ssh:default_algorithms()} | Opts]); init_per_testcase(_TestCase, Config) -> check_std_daemon_works(Config, ?LINE). @@ -327,6 +327,8 @@ no_common_alg_client_disconnects(Config) -> X -> ct:log("¤¤¤¤¤"), ct:fail(X) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%%-------------------------------------------------------------------- diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index 32fdec9842..698af259c8 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -526,6 +526,8 @@ async_read(Config) when is_list(Config) -> ok; Msg -> ct:fail(Msg) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- async_write() -> @@ -593,6 +595,8 @@ pos_read(Config) when is_list(Config) -> ok; Msg -> ct:fail(Msg) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, NewData1 = "hopp", @@ -618,6 +622,8 @@ pos_write(Config) when is_list(Config) -> ok; Msg -> ct:fail(Msg) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, ok = ssh_sftp:pwrite(Sftp, Handle, eof, list_to_binary("!")), diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl index 94a54ec9db..6b03a2b763 100644 --- a/lib/ssh/test/ssh_sftpd_SUITE.erl +++ b/lib/ssh/test/ssh_sftpd_SUITE.erl @@ -683,6 +683,8 @@ reply(Cm, Channel, RBuf) -> closed; {ssh_cm, Cm, Msg} -> ct:fail(Msg) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index 6d568125bb..87eaeec1bc 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -93,9 +93,12 @@ std_connect(Config, Host, Port, ExtraOpts) -> | ExtraOpts]). std_simple_sftp(Host, Port, Config) -> + std_simple_sftp(Host, Port, Config, []). + +std_simple_sftp(Host, Port, Config, Opts) -> UserDir = ?config(priv_dir, Config), DataFile = filename:join(UserDir, "test.data"), - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, []), + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts), {ok, ChannelRef} = ssh_sftp:start_channel(ConnectionRef), Data = crypto:rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)), ok = ssh_sftp:write_file(ChannelRef, DataFile, Data), @@ -104,7 +107,10 @@ std_simple_sftp(Host, Port, Config) -> Data == ReadData. std_simple_exec(Host, Port, Config) -> - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, []), + std_simple_exec(Host, Port, Config, []). + +std_simple_exec(Host, Port, Config, Opts) -> + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts), {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId, "23+21-2.", infinity), Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"42\n">>}}, @@ -157,7 +163,9 @@ loop_io_server(TestCase, Buff0) -> {'EXIT',_, _} -> erlang:display('ssh_test_lib:loop_io_server/2 EXIT'), ok - end. + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) + end. io_request({put_chars, Chars}, TestCase, _, _, Buff) -> reply(TestCase, Chars), @@ -206,6 +214,8 @@ receive_exec_result(Msg) -> Other -> ct:log("Other ~p", [Other]), {unexpected_msg, Other} + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. @@ -286,6 +296,7 @@ setup_dsa(DataDir, UserDir) -> file:make_dir(System), file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")), file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")), +ct:pal("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), setup_dsa_known_host(DataDir, UserDir), setup_dsa_auth_keys(DataDir, UserDir). @@ -294,10 +305,21 @@ setup_rsa(DataDir, UserDir) -> System = filename:join(UserDir, "system"), file:make_dir(System), file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")), - file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key.pub")), + file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")), +ct:pal("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), setup_rsa_known_host(DataDir, UserDir), setup_rsa_auth_keys(DataDir, UserDir). +setup_ecdsa(Size, DataDir, UserDir) -> + file:copy(filename:join(DataDir, "id_ecdsa"++Size), filename:join(UserDir, "id_ecdsa")), + System = filename:join(UserDir, "system"), + file:make_dir(System), + file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")), + file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")), +ct:pal("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), + setup_ecdsa_known_host(Size, System, UserDir), + setup_ecdsa_auth_keys(Size, UserDir, UserDir). + clean_dsa(UserDir) -> del_dirs(filename:join(UserDir, "system")), file:delete(filename:join(UserDir,"id_dsa")), @@ -349,6 +371,11 @@ setup_rsa_known_host(SystemDir, UserDir) -> [{Key, _}] = public_key:ssh_decode(SshBin, public_key), setup_known_hosts(Key, UserDir). +setup_ecdsa_known_host(_Size, SystemDir, UserDir) -> + {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_ecdsa_key.pub")), + [{Key, _}] = public_key:ssh_decode(SshBin, public_key), + setup_known_hosts(Key, UserDir). + setup_known_hosts(Key, UserDir) -> {ok, Hostname} = inet:gethostname(), {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet), @@ -376,6 +403,19 @@ setup_rsa_auth_keys(Dir, UserDir) -> PKey = #'RSAPublicKey'{publicExponent = E, modulus = N}, setup_auth_keys([{ PKey, [{comment, "Test"}]}], UserDir). +setup_ecdsa_auth_keys(Size, Dir, UserDir) -> + {ok, Pem} = file:read_file(filename:join(Dir, "id_ecdsa")), + ECDSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))), + #'ECPrivateKey'{publicKey = Q, + parameters = {namedCurve,Id0}} = ECDSA, + PKey = #'ECPoint'{point = Q}, + Id = case pubkey_cert_records:namedCurves(Id0) of + secp256r1 when Size=="256" -> <<"nistp256">>; + secp384r1 when Size=="384" -> <<"nistp384">>; + secp521r1 when Size=="521" -> <<"nistp521">> + end, + setup_auth_keys([{ {PKey,Id}, [{comment, "Test"}]}], UserDir). + setup_auth_keys(Keys, Dir) -> AuthKeys = public_key:ssh_encode(Keys, auth_keys), AuthKeysFile = filename:join(Dir, "authorized_keys"), @@ -552,4 +592,24 @@ algo_intersection(_, _) -> to_atoms(L) -> lists:map(fun erlang:list_to_atom/1, L). - +%%%---------------------------------------------------------------- +ssh_supports(Alg, SshDefaultAlg_tag) -> + SupAlgs = + case proplists:get_value(SshDefaultAlg_tag, + ssh:default_algorithms()) of + [{_K1,L1}, {_K2,L2}] -> + lists:usort(L1++L2); + L -> + L + end, + if + is_atom(Alg) -> + lists:member(Alg, SupAlgs); + is_list(Alg) -> + case Alg--SupAlgs of + [] -> + true; + UnSup -> + {false,UnSup} + end + end. diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index 104c1f9107..026fe545c1 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -182,23 +182,29 @@ erlang_client_openssh_server_exec_compressed() -> erlang_client_openssh_server_exec_compressed(Config) when is_list(Config) -> CompressAlgs = [zlib, '[email protected]',none], - ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, - {user_interaction, false}, - {preferred_algorithms, - [{compression,CompressAlgs}]}]), - {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), - success = ssh_connection:exec(ConnectionRef, ChannelId, - "echo testing", infinity), - Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}}, - case ssh_test_lib:receive_exec_result(Data) of - expected -> - ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId); - {unexpected_msg,{ssh_cm, ConnectionRef, - {exit_status, ChannelId, 0}} = ExitStatus} -> - ct:log("0: Collected data ~p", [ExitStatus]), - ssh_test_lib:receive_exec_result(Data, ConnectionRef, ChannelId); - Other -> - ct:fail(Other) + case ssh_test_lib:ssh_supports(CompressAlgs, compression) of + {false,L} -> + {skip, io_lib:format("~p compression is not supported",[L])}; + + true -> + ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}, + {preferred_algorithms, + [{compression,CompressAlgs}]}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:exec(ConnectionRef, ChannelId, + "echo testing", infinity), + Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}}, + case ssh_test_lib:receive_exec_result(Data) of + expected -> + ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId); + {unexpected_msg,{ssh_cm, ConnectionRef, + {exit_status, ChannelId, 0}} = ExitStatus} -> + ct:log("0: Collected data ~p", [ExitStatus]), + ssh_test_lib:receive_exec_result(Data, ConnectionRef, ChannelId); + Other -> + ct:fail(Other) + end end. %%-------------------------------------------------------------------- @@ -383,7 +389,7 @@ erlang_server_openssh_client_kexs(Config) when is_list(Config) -> {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {failfun, fun ssh_test_lib:failfun/2}, {preferred_algorithms, - [{kex,ssh_transport:supported_algorithms(kex)}]} + [{kex,ssh_transport:default_algorithms(kex)}]} ]), ct:sleep(500), @@ -425,27 +431,32 @@ erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), KnownHosts = filename:join(PrivDir, "known_hosts"), -%% CompressAlgs = [zlib, '[email protected]'], % Does not work - CompressAlgs = [zlib], - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {preferred_algorithms, - [{compression, CompressAlgs}]}, - {failfun, fun ssh_test_lib:failfun/2}]), + CompressAlgs = [zlib, '[email protected]'], % Does not work +%% CompressAlgs = [zlib], + case ssh_test_lib:ssh_supports(CompressAlgs, compression) of + {false,L} -> + {skip, io_lib:format("~p compression is not supported",[L])}; - ct:sleep(500), + true -> + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {preferred_algorithms, + [{compression, CompressAlgs}]}, + {failfun, fun ssh_test_lib:failfun/2}]), - Cmd = "ssh -p " ++ integer_to_list(Port) ++ - " -o UserKnownHostsFile=" ++ KnownHosts ++ " -C "++ Host ++ " 1+1.", - SshPort = open_port({spawn, Cmd}, [binary]), + ct:sleep(500), - receive - {SshPort,{data, <<"2\n">>}} -> - ok - after ?TIMEOUT -> - ct:fail("Did not receive answer") + Cmd = "ssh -p " ++ integer_to_list(Port) ++ + " -o UserKnownHostsFile=" ++ KnownHosts ++ " -C "++ Host ++ " 1+1.", + SshPort = open_port({spawn, Cmd}, [binary]), - end, - ssh:stop_daemon(Pid). + receive + {SshPort,{data, <<"2\n">>}} -> + ok + after ?TIMEOUT -> + ct:fail("Did not receive answer") + end, + ssh:stop_daemon(Pid) + end. %%-------------------------------------------------------------------- erlang_client_openssh_server_setenv() -> @@ -642,6 +653,8 @@ receive_hej() -> ct:log("Extra info: ~p~n", [Info]), receive_hej() end + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. receive_logout() -> @@ -651,11 +664,15 @@ receive_logout() -> receive <<"Connection closed">> -> ok + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end; Info -> ct:log("Extra info when logging out: ~p~n", [Info]), receive_logout() - end. + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) + end. receive_normal_exit(Shell) -> receive @@ -665,6 +682,8 @@ receive_normal_exit(Shell) -> receive_normal_exit(Shell); Other -> ct:fail({unexpected_msg, Other}) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. extra_logout() -> |