aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/doc/src/notes.xml32
-rw-r--r--lib/ssh/doc/src/ssh.xml121
-rw-r--r--lib/ssh/src/ssh.erl132
-rw-r--r--lib/ssh/src/ssh.hrl14
-rw-r--r--lib/ssh/src/ssh_auth.erl145
-rw-r--r--lib/ssh/src/ssh_auth.hrl2
-rw-r--r--lib/ssh/src/ssh_connection.erl27
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl34
-rw-r--r--lib/ssh/src/ssh_file.erl64
-rw-r--r--lib/ssh/src/ssh_message.erl153
-rw-r--r--lib/ssh/src/ssh_transport.erl510
-rw-r--r--lib/ssh/src/ssh_transport.hrl36
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE.erl84
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl151
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl121
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl175
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl227
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli3
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE.erl13
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl6
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl2
-rw-r--r--lib/ssh/test/ssh_test_lib.erl213
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl285
-rw-r--r--lib/ssh/test/ssh_trpt_test_lib.erl16
36 files changed, 1738 insertions, 870 deletions
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 368bb0f552..bb111c8e0e 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,38 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Add a 1024 group to the list of key group-exchange groups</p>
+ <p>
+ Own Id: OTP-13046</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<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..0e5a0706f5 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -40,21 +40,50 @@
<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>
+ <title>OPTIONS</title>
+ <p>The exact behaviour of some functions can be adjusted with the use of options which are documented together
+ with the functions. Generally could each option be used at most one time in each function call. If given two or more
+ times, the effect is not predictable unless explicitly documented.</p>
+ <p>The options are of different kinds:</p>
+ <taglist>
+ <tag>Limits</tag>
+ <item><p>which alters limits in the system, for example number of simultaneous login attempts.</p></item>
+
+ <tag>Timeouts</tag>
+ <item><p>which give some defined behaviour if too long time elapses before a given event or action,
+ for example time to wait for an answer.</p></item>
+
+ <tag>Callbacks</tag>
+ <item><p>which gives the caller of the function the possibility to execute own code on some events,
+ for example calling an own logging function or to perform an own login function</p></item>
+
+ <tag>Behaviour</tag>
+ <item><p>which changes the systems behaviour.</p></item>
+ </taglist>
+ </section>
+
+ <section>
<title>DATA TYPES</title>
<p>Type definitions that are used more than once in
this module, or abstractions to indicate the intended use of the data
@@ -456,21 +485,82 @@ kex is implicit but public_key is set explicitly.</p>
</warning>
</item>
- <tag><c><![CDATA[{dh_gex_groups, [{Size=integer(),G=integer(),P=integer()}] | {file,filename()} }]]></c></tag>
+ <tag><c><![CDATA[{dh_gex_groups, [{Size=integer(),G=integer(),P=integer()}] | {file,filename()} {ssh_moduli_file,filename()} }]]></c></tag>
<item>
- <p>Sets the groups that the server may choose among when diffie-hellman-group-exchange is negotiated.
- See RFC 4419 for details.
+ <p>Defines the groups the server may choose among when diffie-hellman-group-exchange is negotiated.
+ See RFC 4419 for details. The three variants of this option are:
</p>
- <p>If the parameter is <c>{file,filename()}</c>, the file must exist and have one or more three-tuples terminated by a dot. The interpretation is as if the tuples had been given directly in the option. The file is read when the daemon starts.
+ <taglist>
+ <tag><c>{Size=integer(),G=integer(),P=integer()}</c></tag>
+ <item>The groups are given explicitly in this list. There may be several elements with the same <c>Size</c>.
+ In such a case, the server will choose one randomly in the negotiated Size.
+ </item>
+ <tag><c>{file,filename()}</c></tag>
+ <item>The file must have one or more three-tuples <c>{Size=integer(),G=integer(),P=integer()}</c>
+ terminated by a dot. The file is read when the daemon starts.
+ </item>
+ <tag><c>{ssh_moduli_file,filename()}</c></tag>
+ <item>The file must be in
+ <seealso marker="public_key:public_key#dh_gex_group/4">ssh-keygen moduli file format</seealso>.
+ The file is read when the daemon starts.
+ </item>
+ </taglist>
+ <p>The default list is fetched from the
+ <seealso marker="public_key:public_key#dh_gex_group/4">public_key</seealso> application.
</p>
</item>
- <tag><c><![CDATA[{pwdfun, fun(User::string(), password::string()) -> boolean()}]]></c></tag>
+ <tag><c><![CDATA[{dh_gex_limits,{Min=integer(),Max=integer()}}]]></c></tag>
+ <item>
+ <p>Limits what a client can ask for in diffie-hellman-group-exchange.
+ The limits will be
+ <c>{MaxUsed = min(MaxClient,Max), MinUsed = max(MinClient,Min)}</c> where <c>MaxClient</c> and
+ <c>MinClient</c> are the values proposed by a connecting client.
+ </p>
+ <p>The default value is <c>{0,infinity}</c>.
+ </p>
+ <p>If <c>MaxUsed &lt; MinUsed</c> in a key exchange, it will fail with a disconnect.
+ </p>
+ <p>See RFC 4419 for the function of the Max and Min values.</p>
+ </item>
+
+ <tag><c><![CDATA[{pwdfun, fun(User::string(), Password::string(), PeerAddress::{ip_adress(),port_number()}, State::any()) -> boolean() | disconnect | {boolean(),any()} }]]></c></tag>
+ <item>
+ <p>Provides a function for password validation. This could used for calling an external system or if
+ passwords should be stored as a hash. The fun returns:
+ <list type="bulleted">
+ <item><c>true</c> if the user and password is valid and</item>
+ <item><c>false</c> otherwise.</item>
+ </list>
+ </p>
+ <p>This fun can also be used to make delays in authentication tries for example by calling
+ <seealso marker="stdlib:timer#sleep/1">timer:sleep/1</seealso>. To facilitate counting of failed tries
+ the <c>State</c> variable could be used. This state is per connection only. The first time the pwdfun
+ is called for a connection, the <c>State</c> variable has the value <c>undefined</c>.
+ The pwdfun can return - in addition to the values above - a new state
+ as:
+ <list type="bulleted">
+ <item><c>{true, NewState:any()}</c> if the user and password is valid or</item>
+ <item><c>{false, NewState:any()}</c> if the user or password is invalid</item>
+ </list>
+ </p>
+ <p>A third usage is to block login attempts from a missbehaving peer. The <c>State</c> described above
+ can be used for this. In addition to the responses above, the following return value is introduced:
+ <list type="bulleted">
+ <item><c>disconnect</c> if the connection should be closed immediately after sending a SSH_MSG_DISCONNECT
+ message.</item>
+ </list>
+ </p>
+ </item>
+
+ <tag><c><![CDATA[{pwdfun, fun(User::string(), Password::string()) -> boolean()}]]></c></tag>
<item>
<p>Provides a function for password validation. This function is called
with user and password as strings, and returns
<c><![CDATA[true]]></c> if the password is valid and
<c><![CDATA[false]]></c> otherwise.</p>
+ <p>This option (<c>{pwdfun,fun/2}</c>) is the same as a subset of the previous
+ (<c>{pwdfun,fun/4}</c>). It is kept for compatibility.</p>
</item>
<tag><c><![CDATA[{negotiation_timeout, integer()}]]></c></tag>
@@ -501,6 +591,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..5bde184070 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -33,7 +33,8 @@
default_algorithms/0,
stop_listener/1, stop_listener/2, stop_listener/3,
stop_daemon/1, stop_daemon/2, stop_daemon/3,
- shell/1, shell/2, shell/3]).
+ shell/1, shell/2, shell/3
+ ]).
%%--------------------------------------------------------------------
-spec start() -> ok | {error, term()}.
@@ -117,9 +118,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.
@@ -337,6 +338,8 @@ handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{key_cb, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{keyboard_interact_fun, _} = Opt | Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
%%Backwards compatibility
handle_option([{allow_user_interaction, Value} | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option({user_interaction, Value}) | SshOptions]);
@@ -385,12 +388,15 @@ 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) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([parallel_login|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]);
+%% (Is handled by proplists:unfold above:)
+%% handle_option([parallel_login|Rest], SocketOptions, SshOptions) ->
+%% handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]);
handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{id_string, _ID} = Opt|Rest], SocketOptions, SshOptions) ->
@@ -417,32 +423,74 @@ handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) ->
Opt;
handle_ssh_option({preferred_algorithms,[_|_]} = Opt) ->
handle_pref_algs(Opt);
-handle_ssh_option({dh_gex_groups,L=[{I1,I2,I3}|_]}) when is_integer(I1), I1>0,
- is_integer(I2), I2>0,
- is_integer(I3), I3>0 ->
- {dh_gex_groups, lists:map(fun({N,G,P}) -> {N,{G,P}} end, L)};
-handle_ssh_option({dh_gex_groups,{file,File=[C|_]}}=Opt) when is_integer(C), C>0 ->
- %% A string, (file name)
- case file:consult(File) of
- {ok, List} ->
- try handle_ssh_option({dh_gex_groups,List}) of
- {dh_gex_groups,_} = NewOpt ->
- NewOpt
- catch
- _:_ ->
- throw({error, {{eoptions, Opt}, "Bad format in file"}})
- end;
- Error ->
- throw({error, {{eoptions, Opt},{"Error reading file",Error}}})
- end;
+
+handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) ->
+ {dh_gex_groups,
+ collect_per_size(
+ lists:foldl(
+ fun({N,G,P}, Acc) when is_integer(N),N>0,
+ is_integer(G),G>0,
+ is_integer(P),P>0 ->
+ [{N,{G,P}} | Acc];
+ ({N,{G,P}}, Acc) when is_integer(N),N>0,
+ is_integer(G),G>0,
+ is_integer(P),P>0 ->
+ [{N,{G,P}} | Acc];
+ ({N,GPs}, Acc) when is_list(GPs) ->
+ lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0,
+ is_integer(Pi),Pi>0 ->
+ [{N,{Gi,Pi}} | Acci]
+ end, Acc, GPs)
+ end, [], L0))};
+
+handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0,
+ Tag == file ;
+ Tag == ssh_moduli_file ->
+ {ok,GroupDefs} =
+ case Tag of
+ file ->
+ file:consult(File);
+ ssh_moduli_file ->
+ case file:open(File,[read]) of
+ {ok,D} ->
+ try
+ {ok,Moduli} = read_moduli_file(D, 1, []),
+ file:close(D),
+ {ok, Moduli}
+ catch
+ _:_ ->
+ throw({error, {{eoptions, Opt}, "Bad format in file "++File}})
+ end;
+ {error,enoent} ->
+ throw({error, {{eoptions, Opt}, "File not found:"++File}});
+ {error,Error} ->
+ throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}})
+ end
+ end,
+
+ try
+ handle_ssh_option({dh_gex_groups,GroupDefs})
+ catch
+ _:_ ->
+ throw({error, {{eoptions, Opt}, "Bad format in file: "++File}})
+ end;
+
+
+handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0,
+ is_integer(Max), Max>=Min ->
+ %% Server
+ Opt;
handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0,
is_integer(I), I>=Min,
is_integer(Max), Max>=I ->
+ %% Client
Opt;
handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity ->
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 ->
@@ -457,10 +505,14 @@ handle_ssh_option({password, Value} = Opt) when is_list(Value) ->
Opt;
handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)->
Opt;
-handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value) ->
+handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,2) ->
+ Opt;
+handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,4) ->
Opt;
handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) ->
Opt;
+handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3) ->
+ Opt;
handle_ssh_option({compression, Value} = Opt) when is_atom(Value) ->
Opt;
handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module),
@@ -655,3 +707,33 @@ directory_exist_readable(Dir) ->
+collect_per_size(L) ->
+ lists:foldr(
+ fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc];
+ ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc]
+ end, [], lists:sort(L)).
+
+read_moduli_file(D, I, Acc) ->
+ case io:get_line(D,"") of
+ {error,Error} ->
+ {error,Error};
+ eof ->
+ {ok, Acc};
+ "#" ++ _ -> read_moduli_file(D, I+1, Acc);
+ <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc);
+ Data ->
+ Line = if is_binary(Data) -> binary_to_list(Data);
+ is_list(Data) -> Data
+ end,
+ try
+ [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"),
+ M = {list_to_integer(Size),
+ {list_to_integer(G), list_to_integer(P,16)}
+ },
+ read_moduli_file(D, I+1, [M|Acc])
+ catch
+ _:_ ->
+ read_moduli_file(D, I+1, Acc)
+ end
+ end.
+
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index da64e4abf9..4ad936f742 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -37,13 +37,16 @@
-define(FALSE, 0).
-define(TRUE, 1).
%% basic binary constructors
--define(BOOLEAN(X), X:8/unsigned-big-integer).
--define(BYTE(X), X:8/unsigned-big-integer).
--define(UINT16(X), X:16/unsigned-big-integer).
--define(UINT32(X), X:32/unsigned-big-integer).
--define(UINT64(X), X:64/unsigned-big-integer).
+-define(BOOLEAN(X), (X):8/unsigned-big-integer).
+-define(BYTE(X), (X):8/unsigned-big-integer).
+-define(UINT16(X), (X):16/unsigned-big-integer).
+-define(UINT32(X), (X):32/unsigned-big-integer).
+-define(UINT64(X), (X):64/unsigned-big-integer).
-define(STRING(X), ?UINT32((size(X))), (X)/binary).
+-define(DEC_BIN(X,Len), ?UINT32(Len), X:Len/binary ).
+-define(DEC_MPINT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ).
+
%% building macros
-define(boolean(X),
case X of
@@ -135,6 +138,7 @@
kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive"
userauth_preference,
available_host_keys,
+ pwdfun_user_state,
authenticated = false
}).
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 726f52132f..4967a2e4cd 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);
+ {ok, PrivKey} ->
+ StrAlgo = atom_to_list(Alg),
+ case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of
+ not_ok ->
+ not_ok;
+ PubKeyBlob ->
+ SigData = build_sig_data(SessionId,
+ User, Service, PubKeyBlob, StrAlgo),
+ Sig = ssh_transport:sign(SigData, Hash, PrivKey),
+ 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,
@@ -173,15 +174,15 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
#ssh{opts = Opts,
userauth_supported_methods = Methods} = Ssh) ->
Password = unicode:characters_to_list(BinPwd),
- case check_password(User, Password, Opts) of
- true ->
+ case check_password(User, Password, Opts, Ssh) of
+ {true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
- false ->
+ ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+ {false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
authentications = Methods,
- partial_success = false}, Ssh)}
+ partial_success = false}, Ssh1)}
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -334,16 +335,16 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
kb_tries_left = KbTriesLeft,
user = User,
userauth_supported_methods = Methods} = Ssh) ->
- case check_password(User, unicode:characters_to_list(Password), Opts) of
- true ->
+ case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of
+ {true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
- false ->
+ ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+ {false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
authentications = Methods,
partial_success = false},
- Ssh#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}
+ Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}
)}
end;
@@ -355,8 +356,6 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{},
language = "en"}).
-default_public_key_algorithms() -> ?PREFERRED_PK_ALGS.
-
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -365,6 +364,11 @@ method_preference(Algs) ->
[{"publickey", ?MODULE, publickey_msg, [A]} | Acc]
end,
[{"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
{"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
],
Algs).
@@ -388,13 +392,34 @@ user_name(Opts) ->
{ok, User}
end.
-check_password(User, Password, Opts) ->
+check_password(User, Password, Opts, Ssh) ->
case proplists:get_value(pwdfun, Opts) of
undefined ->
Static = get_password_option(Opts, User),
- Password == Static;
- Cheker ->
- Cheker(User, Password)
+ {Password == Static, Ssh};
+
+ Checker when is_function(Checker,2) ->
+ {Checker(User, Password), Ssh};
+
+ Checker when is_function(Checker,4) ->
+ #ssh{pwdfun_user_state = PrivateState,
+ peer = {_,PeerAddr={_,_}}
+ } = Ssh,
+ case Checker(User, Password, PeerAddr, PrivateState) of
+ true ->
+ {true,Ssh};
+ false ->
+ {false,Ssh};
+ {true,NewState} ->
+ {true, Ssh#ssh{pwdfun_user_state=NewState}};
+ {false,NewState} ->
+ {false, Ssh#ssh{pwdfun_user_state=NewState}};
+ disconnect ->
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description =
+ "Unable to connect using the available authentication methods",
+ language = ""})
+ end
end.
get_password_option(Opts, User) ->
@@ -431,10 +456,7 @@ 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".
+
decode_keyboard_interactive_prompts(_NumPrompts, Data) ->
ssh_message:decode_keyboard_interactive_prompts(Data, []).
@@ -455,14 +477,14 @@ keyboard_interact_get_responses(false, undefined, undefined, _, _, _, [Prompt|_]
ssh_no_io:read_line(Prompt, Opts); %% Throws error as keyboard interaction is not allowed
keyboard_interact_get_responses(true, undefined, _,IoCb, Name, Instr, PromptInfos, Opts, _) ->
keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts);
-keyboard_interact_get_responses(true, Fun, _, Name, Instr, PromptInfos, _, _, NumPrompts) ->
+keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos, _Opts, NumPrompts) ->
keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts).
keyboard_interact(IoCb, Name, Instr, Prompts, Opts) ->
- if Name /= "" -> IoCb:format("~s", [Name]);
+ if Name /= "" -> IoCb:format("~s~n", [Name]);
true -> ok
end,
- if Instr /= "" -> IoCb:format("~s", [Instr]);
+ if Instr /= "" -> IoCb:format("~s~n", [Instr]);
true -> ok
end,
lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts);
@@ -485,23 +507,18 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
language = "en"}})
end.
-decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary,
- ?UINT32(Len1), E:Len1/big-signed-integer-unit:8,
- ?UINT32(Len2), N:Len2/big-signed-integer-unit:8>>
- ,"ssh-rsa") ->
- {ok, #'RSAPublicKey'{publicExponent = E, modulus = N}};
-decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary,
- ?UINT32(Len1), P:Len1/big-signed-integer-unit:8,
- ?UINT32(Len2), Q:Len2/big-signed-integer-unit:8,
- ?UINT32(Len3), G:Len3/big-signed-integer-unit:8,
- ?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(_, _) ->
- {error, bad_format}.
-
-encode_public_key(#'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]).
+decode_public_key_v2(Bin, _Type) ->
+ try
+ public_key:ssh_decode(Bin, ssh2_pubkey)
+ of
+ Key -> {ok, Key}
+ catch
+ _:_ -> {error, bad_format}
+ end.
+
+encode_public_key(_Alg, Key) ->
+ try
+ public_key:ssh_encode(Key, ssh2_pubkey)
+ catch
+ _:_ -> not_ok
+ end.
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..a2d1b5b810 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,19 +622,29 @@ 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),
{next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
- end.
-
+ end;
+userauth_keyboard_interactive(Msg = #ssh_msg_userauth_failure{},
+ #state{ssh_params = Ssh0 =
+ #ssh{role = client,
+ userauth_preference = Prefs0}}
+ = State) ->
+ Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Prefs0,
+ Method =/= "keyboard-interactive"],
+ userauth(Msg, State#state{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}).
+
-userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_failure{}, State) ->
+userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_failure{},
+ #state{ssh_params = #ssh{role = client}} = State) ->
userauth(Msg, State);
-userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_success{}, State) ->
+userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_success{},
+ #state{ssh_params = #ssh{role = client}} = State) ->
userauth(Msg, State).
%%--------------------------------------------------------------------
@@ -1266,9 +1276,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 +1290,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..c087ce14d7 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,13 @@ key_match(#'RSAPublicKey'{}, 'ssh-rsa') ->
true;
key_match({_, #'Dss-Parms'{}}, 'ssh-dss') ->
true;
+key_match({#'ECPoint'{},{namedCurve,Curve}}, Alg) ->
+ case atom_to_list(Alg) of
+ "ecdsa-sha2-"++IdS ->
+ Curve == public_key:ssh_curvename2oid(list_to_binary(IdS));
+ _ ->
+ false
+ end;
key_match(_, _) ->
false.
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index cb1dcb67c5..b6c4496be2 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -30,7 +30,7 @@
-include("ssh_auth.hrl").
-include("ssh_transport.hrl").
--export([encode/1, decode/1, encode_host_key/1, decode_keyboard_interactive_prompts/2]).
+-export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]).
encode(#ssh_msg_global_request{
name = Name,
@@ -227,8 +227,8 @@ encode(#ssh_msg_kexdh_reply{
f = F,
h_sig = Signature
}) ->
- EncKey = encode_host_key(Key),
- EncSign = encode_sign(Key, Signature),
+ EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncSign = encode_signature(Key, Signature),
ssh_bits:encode([?SSH_MSG_KEXDH_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]);
encode(#ssh_msg_kex_dh_gex_request{
@@ -255,16 +255,16 @@ encode(#ssh_msg_kex_dh_gex_reply{
f = F,
h_sig = Signature
}) ->
- EncKey = encode_host_key(Key),
- EncSign = encode_sign(Key, Signature),
+ EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncSign = encode_signature(Key, Signature),
ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]);
encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) ->
ssh_bits:encode([?SSH_MSG_KEX_ECDH_INIT, Q_c], [byte, mpint]);
encode(#ssh_msg_kex_ecdh_reply{public_host_key = Key, q_s = Q_s, h_sig = Sign}) ->
- EncKey = encode_host_key(Key),
- EncSign = encode_sign(Key, Sign),
+ EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncSign = encode_signature(Key, Sign),
ssh_bits:encode([?SSH_MSG_KEX_ECDH_REPLY, EncKey, Q_s, EncSign], [byte, binary, mpint, binary]);
encode(#ssh_msg_ignore{data = Data}) ->
@@ -280,8 +280,7 @@ encode(#ssh_msg_debug{always_display = Bool,
%% Connection Messages
-decode(<<?BYTE(?SSH_MSG_GLOBAL_REQUEST), ?UINT32(Len), Name:Len/binary,
- ?BYTE(Bool), Data/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_GLOBAL_REQUEST), ?DEC_BIN(Name,__0), ?BYTE(Bool), Data/binary>>) ->
#ssh_msg_global_request{
name = Name,
want_reply = erl_boolean(Bool),
@@ -292,8 +291,7 @@ decode(<<?BYTE(?SSH_MSG_REQUEST_SUCCESS), Data/binary>>) ->
decode(<<?BYTE(?SSH_MSG_REQUEST_FAILURE)>>) ->
#ssh_msg_request_failure{};
decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN),
- ?UINT32(Len), Type:Len/binary,
- ?UINT32(Sender), ?UINT32(Window), ?UINT32(Max),
+ ?DEC_BIN(Type,__0), ?UINT32(Sender), ?UINT32(Window), ?UINT32(Max),
Data/binary>>) ->
#ssh_msg_channel_open{
channel_type = binary_to_list(Type),
@@ -313,7 +311,7 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN_CONFIRMATION), ?UINT32(Recipient), ?UINT32(
data = Data
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN_FAILURE), ?UINT32(Recipient), ?UINT32(Reason),
- ?UINT32(Len0), Desc:Len0/binary, ?UINT32(Len1), Lang:Len1/binary >>) ->
+ ?DEC_BIN(Desc,__0), ?DEC_BIN(Lang,__1) >> ) ->
#ssh_msg_channel_open_failure{
recipient_channel = Recipient,
reason = Reason,
@@ -326,13 +324,13 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_WINDOW_ADJUST), ?UINT32(Recipient), ?UINT32(Byte
bytes_to_add = Bytes
};
-decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?UINT32(Len), Data:Len/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?DEC_BIN(Data,__0)>>) ->
#ssh_msg_channel_data{
recipient_channel = Recipient,
data = Data
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient),
- ?UINT32(DataType), ?UINT32(Len), Data:Len/binary>>) ->
+ ?UINT32(DataType), ?DEC_BIN(Data,__0)>>) ->
#ssh_msg_channel_extended_data{
recipient_channel = Recipient,
data_type_code = DataType,
@@ -347,8 +345,7 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>) ->
recipient_channel = Recipient
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_REQUEST), ?UINT32(Recipient),
- ?UINT32(Len), RequestType:Len/binary,
- ?BYTE(Bool), Data/binary>>) ->
+ ?DEC_BIN(RequestType,__0), ?BYTE(Bool), Data/binary>>) ->
#ssh_msg_channel_request{
recipient_channel = Recipient,
request_type = unicode:characters_to_list(RequestType),
@@ -366,9 +363,7 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_FAILURE), ?UINT32(Recipient)>>) ->
%%% Auth Messages
decode(<<?BYTE(?SSH_MSG_USERAUTH_REQUEST),
- ?UINT32(Len0), User:Len0/binary,
- ?UINT32(Len1), Service:Len1/binary,
- ?UINT32(Len2), Method:Len2/binary,
+ ?DEC_BIN(User,__0), ?DEC_BIN(Service,__1), ?DEC_BIN(Method,__2),
Data/binary>>) ->
#ssh_msg_userauth_request{
user = unicode:characters_to_list(User),
@@ -378,7 +373,7 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_REQUEST),
};
decode(<<?BYTE(?SSH_MSG_USERAUTH_FAILURE),
- ?UINT32(Len0), Auths:Len0/binary,
+ ?DEC_BIN(Auths,__0),
?BYTE(Bool)>>) ->
#ssh_msg_userauth_failure {
authentications = unicode:characters_to_list(Auths),
@@ -388,16 +383,14 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_FAILURE),
decode(<<?BYTE(?SSH_MSG_USERAUTH_SUCCESS)>>) ->
#ssh_msg_userauth_success{};
-decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER),
- ?UINT32(Len0), Banner:Len0/binary,
- ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER), ?DEC_BIN(Banner,__0), ?DEC_BIN(Lang,__1) >>) ->
#ssh_msg_userauth_banner{
message = Banner,
language = Lang
};
-decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary,
- ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary,
+decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST),
+ ?DEC_BIN(Name,__0), ?DEC_BIN(Inst,__1), ?DEC_BIN(Lang,__2),
?UINT32(NumPromtps), Data/binary>>) ->
#ssh_msg_userauth_info_request{
name = Name,
@@ -407,15 +400,14 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary,
data = Data};
%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST:
-decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?UINT32(Len0), Prompt:Len0/binary,
- ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?DEC_BIN(Prompt,__0), ?DEC_BIN(Lang,__1) >>) ->
#ssh_msg_userauth_passwd_changereq{
prompt = Prompt,
languge = Lang
};
%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST:
-decode(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?UINT32(Len), Alg:Len/binary, KeyBlob/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?DEC_BIN(Alg,__0), KeyBlob/binary>>) ->
#ssh_msg_userauth_pk_ok{
algorithm_name = Alg,
key_blob = KeyBlob
@@ -430,18 +422,15 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) ->
decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) ->
decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10);
-decode(<<"dh",?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/big-signed-integer-unit:8>>) ->
+decode(<<"dh",?BYTE(?SSH_MSG_KEXDH_INIT), ?DEC_MPINT(E,__0)>>) ->
#ssh_msg_kexdh_init{e = E
};
-decode(<<"dh", ?BYTE(?SSH_MSG_KEXDH_REPLY),
- ?UINT32(Len0), Key:Len0/binary,
- ?UINT32(Len1), F:Len1/big-signed-integer-unit:8,
- ?UINT32(Len2), Hashsign:Len2/binary>>) ->
+decode(<<"dh", ?BYTE(?SSH_MSG_KEXDH_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) ->
#ssh_msg_kexdh_reply{
- public_host_key = decode_host_key(Key),
+ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
f = F,
- h_sig = decode_sign(Hashsign)
+ h_sig = decode_signature(Hashsign)
};
decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST), ?UINT32(Min), ?UINT32(N), ?UINT32(Max)>>) ->
@@ -456,57 +445,48 @@ decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) ->
n = N
};
-decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP),
- ?UINT32(Len0), Prime:Len0/big-signed-integer-unit:8,
- ?UINT32(Len1), Generator:Len1/big-signed-integer-unit:8>>) ->
+decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), ?DEC_MPINT(Prime,__0), ?DEC_MPINT(Generator,__1) >>) ->
#ssh_msg_kex_dh_gex_group{
p = Prime,
g = Generator
};
-decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_INIT), ?UINT32(Len), E:Len/big-signed-integer-unit:8>>) ->
+decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_INIT), ?DEC_MPINT(E,__0)>>) ->
#ssh_msg_kex_dh_gex_init{
e = E
};
-decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REPLY),
- ?UINT32(Len0), Key:Len0/binary,
- ?UINT32(Len1), F:Len1/big-signed-integer-unit:8,
- ?UINT32(Len2), Hashsign:Len2/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) ->
#ssh_msg_kex_dh_gex_reply{
- public_host_key = decode_host_key(Key),
+ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
f = F,
- h_sig = decode_sign(Hashsign)
+ h_sig = decode_signature(Hashsign)
};
-decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT),
- ?UINT32(Len0), Q_c:Len0/big-signed-integer-unit:8>>) ->
+decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_MPINT(Q_c,__0)>>) ->
#ssh_msg_kex_ecdh_init{
q_c = Q_c
};
decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY),
- ?UINT32(Len1), Key:Len1/binary,
- ?UINT32(Len2), Q_s:Len2/big-signed-integer-unit:8,
- ?UINT32(Len3), Sig:Len3/binary>>) ->
+ ?DEC_BIN(Key,__1), ?DEC_MPINT(Q_s,__2), ?DEC_BIN(Sig,__3)>>) ->
#ssh_msg_kex_ecdh_reply{
- public_host_key = decode_host_key(Key),
+ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
q_s = Q_s,
- h_sig = decode_sign(Sig)
+ h_sig = decode_signature(Sig)
};
-decode(<<?SSH_MSG_SERVICE_REQUEST, ?UINT32(Len0), Service:Len0/binary>>) ->
+decode(<<?SSH_MSG_SERVICE_REQUEST, ?DEC_BIN(Service,__0)>>) ->
#ssh_msg_service_request{
name = unicode:characters_to_list(Service)
};
-decode(<<?SSH_MSG_SERVICE_ACCEPT, ?UINT32(Len0), Service:Len0/binary>>) ->
+decode(<<?SSH_MSG_SERVICE_ACCEPT, ?DEC_BIN(Service,__0)>>) ->
#ssh_msg_service_accept{
name = unicode:characters_to_list(Service)
};
-decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code),
- ?UINT32(Len0), Desc:Len0/binary, ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code), ?DEC_BIN(Desc,__0), ?DEC_BIN(Lang,__1)>>) ->
#ssh_msg_disconnect{
code = Code,
description = unicode:characters_to_list(Desc),
@@ -514,8 +494,7 @@ decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code),
};
%% Accept bad disconnects from ancient openssh clients that doesn't send language tag. Use english as a work-around.
-decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code),
- ?UINT32(Len0), Desc:Len0/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code), ?DEC_BIN(Desc,__0)>>) ->
#ssh_msg_disconnect{
code = Code,
description = unicode:characters_to_list(Desc),
@@ -525,21 +504,25 @@ decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code),
decode(<<?SSH_MSG_NEWKEYS>>) ->
#ssh_msg_newkeys{};
-decode(<<?BYTE(?SSH_MSG_IGNORE), ?UINT32(Len), Data:Len/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_IGNORE), ?DEC_BIN(Data,__0)>>) ->
#ssh_msg_ignore{data = Data};
decode(<<?BYTE(?SSH_MSG_UNIMPLEMENTED), ?UINT32(Seq)>>) ->
#ssh_msg_unimplemented{sequence = Seq};
-decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?UINT32(Len0), Msg:Len0/binary,
- ?UINT32(Len1), Lang:Len1/binary>>) ->
+decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__1)>>) ->
#ssh_msg_debug{always_display = erl_boolean(Bool),
message = Msg,
language = Lang}.
+%%%================================================================
+%%%
+%%% Helper functions
+%%%
+
decode_keyboard_interactive_prompts(<<>>, Acc) ->
lists:reverse(Acc);
-decode_keyboard_interactive_prompts(<<?UINT32(Len), Prompt:Len/binary, ?BYTE(Bool), Bin/binary>>,
+decode_keyboard_interactive_prompts(<<?DEC_BIN(Prompt,__0), ?BYTE(Bool), Bin/binary>>,
Acc) ->
decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]).
@@ -555,43 +538,25 @@ decode_kex_init(<<?BYTE(Bool)>>, Acc, 0) ->
%% See rfc 4253 7.1
X = 0,
list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc]));
-decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) ->
+decode_kex_init(<<?DEC_BIN(Data,__0), Rest/binary>>, Acc, N) ->
Names = string:tokens(unicode:characters_to_list(Data), ","),
decode_kex_init(Rest, [Names | Acc], N -1).
+%%%================================================================
+%%%
+%%% Signature decode/encode
+%%%
-decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) ->
+decode_signature(<<?DEC_BIN(_Alg,__0), ?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,
- modulus = N};
-
-decode_host_key(<<"ssh-dss">>,
- <<?UINT32(Len0), P:Len0/big-signed-integer-unit:8,
- ?UINT32(Len1), Q:Len1/big-signed-integer-unit:8,
- ?UINT32(Len2), G:Len2/big-signed-integer-unit:8,
- ?UINT32(Len3), Y:Len3/big-signed-integer-unit:8>>) ->
- {Y, #'Dss-Parms'{p = P,
- q = Q,
- g = G}}.
-
-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(#'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]).
-encode_sign(#'RSAPrivateKey'{}, Signature) ->
+
+encode_signature(#'RSAPublicKey'{}, Signature) ->
ssh_bits:encode(["ssh-rsa", Signature],[string, binary]);
-encode_sign(#'DSAPrivateKey'{}, Signature) ->
- ssh_bits:encode(["ssh-dss", Signature],[string, binary]).
+encode_signature({_, #'Dss-Parms'{}}, Signature) ->
+ ssh_bits:encode(["ssh-dss", Signature],[string, binary]);
+encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) ->
+ CurveName = public_key:oid2ssh_curvename(OID),
+ ssh_bits:encode([<<"ecdsa-sha2-",CurveName/binary>>, Signature], [binary,binary]).
+
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 2b6f0a3cdc..d61fc76c0a 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -44,7 +44,8 @@
handle_kexdh_reply/2,
handle_kex_ecdh_init/2,
handle_kex_ecdh_reply/2,
- unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1,
+ extract_public_key/1,
+ unpack/3, decompress/2, ssh_packet/2, pack/2, pack/3, msg_data/1,
sign/3, verify/4]).
%%%----------------------------------------------------------------------------
@@ -65,9 +66,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) -> % Example of how to disable an algorithm
+%% supported_algorithms(kex, ['ecdh-sha2-nistp521']);
default_algorithms(Alg) ->
supported_algorithms(Alg).
@@ -79,18 +79,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,20 +107,22 @@ 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',
+ 'zlib'
+ ]).
-
-supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) ->
- [{client2server,As1},{server2client,As2}] = supported_algorithms(Key),
- [{client2server,As1--BL1},{server2client,As2--BL2}];
-supported_algorithms(Key, BlackList) ->
- supported_algorithms(Key) -- BlackList.
+%% Dialyzer complains when not called...supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) ->
+%% Dialyzer complains when not called... [{client2server,As1},{server2client,As2}] = supported_algorithms(Key),
+%% Dialyzer complains when not called... [{client2server,As1--BL1},{server2client,As2--BL2}];
+%% Dialyzer complains when not called...supported_algorithms(Key, BlackList) ->
+%% Dialyzer complains when not called... supported_algorithms(Key) -- BlackList.
select_crypto_supported(L) ->
Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()],
@@ -124,10 +135,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}].
@@ -303,9 +329,7 @@ verify_algorithm(#alg{encrypt = undefined}) -> false;
verify_algorithm(#alg{decrypt = undefined}) -> false;
verify_algorithm(#alg{compress = undefined}) -> false;
verify_algorithm(#alg{decompress = undefined}) -> false;
-
-verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex));
-verify_algorithm(_) -> false.
+verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex)).
%%%----------------------------------------------------------------
%%%
@@ -319,11 +343,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,
@@ -354,13 +379,15 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
1=<E, E=<(P-1) ->
{Public, Private} = generate_key(dh, [P,G]),
K = compute_key(dh, E, Private, [P,G]),
- Key = get_host_key(Ssh0),
- H = kex_h(Ssh0, Key, E, Public, K),
- H_SIG = sign_host_key(Ssh0, Key, H),
- {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = Key,
- f = Public,
- h_sig = H_SIG
- }, Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0),
+ MyPubHostKey = extract_public_key(MyPrivHostKey),
+ H = kex_h(Ssh0, MyPubHostKey, E, Public, K),
+ H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ {SshPacket, Ssh1} =
+ ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey,
+ f = Public,
+ h_sig = H_SIG
+ }, Ssh0),
{ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
shared_secret = K,
exchanged_hash = H,
@@ -375,7 +402,7 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
})
end.
-handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey,
+handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey,
f = F,
h_sig = H_SIG},
#ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) ->
@@ -383,9 +410,9 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey,
if
1=<F, F=<(P-1)->
K = compute_key(dh, F, Private, [P,G]),
- H = kex_h(Ssh0, HostKey, Public, F, K),
+ H = kex_h(Ssh0, PeerPubHostKey, Public, F, K),
- case verify_host_key(Ssh0, HostKey, H, H_SIG) of
+ case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
{ok, SshPacket, Ssh#ssh{shared_secret = K,
@@ -414,19 +441,29 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey,
%%%
%%% diffie-hellman-group-exchange-sha1
%%%
-handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min,
+handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0,
n = NBits,
- max = Max},
- Ssh0=#ssh{opts=Opts}) when Min=<NBits, NBits=<Max ->
+ max = Max0},
+ Ssh0=#ssh{opts=Opts}) when Min0=<NBits, NBits=<Max0 ->
%% server
- {G, P} = dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)),
- {Public, Private} = generate_key(dh, [P,G]),
- {SshPacket, Ssh} =
- ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
- {ok, SshPacket,
- Ssh#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {Min, Max, NBits}
- }};
+ {Min, Max} = adjust_gex_min_max(Min0, Max0, Opts),
+ case public_key:dh_gex_group(Min, NBits, Max,
+ proplists:get_value(dh_gex_groups,Opts)) of
+ {ok, {_Sz, {G,P}}} ->
+ {Public, Private} = generate_key(dh, [P,G]),
+ {SshPacket, Ssh} =
+ ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
+ {ok, SshPacket,
+ Ssh#ssh{keyex_key = {{Private, Public}, {G, P}},
+ keyex_info = {Min, Max, NBits}
+ }};
+ {error,_} ->
+ throw(#ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "No possible diffie-hellman-group-exchange group found",
+ language = ""})
+ end;
+
handle_kex_dh_gex_request(_, _) ->
throw({{error,bad_ssh_msg_kex_dh_gex_request},
#ssh_msg_disconnect{
@@ -435,6 +472,26 @@ handle_kex_dh_gex_request(_, _) ->
language = ""}
}).
+
+adjust_gex_min_max(Min0, Max0, Opts) ->
+ case proplists:get_value(dh_gex_limits, Opts) of
+ undefined ->
+ {Min0, Max0};
+ {Min1, Max1} ->
+ Min2 = max(Min0, Min1),
+ Max2 = min(Max0, Max1),
+ if
+ Min2 =< Max2 ->
+ {Min2, Max2};
+ Max2 < Min2 ->
+ throw(#ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "No possible diffie-hellman-group-exchange group possible",
+ language = ""})
+ end
+ end.
+
+
handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
%% client
{Public, Private} = generate_key(dh, [P,G]),
@@ -454,11 +511,12 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E},
K = compute_key(dh, E, Private, [P,G]),
if
1<K, K<(P-1) ->
- HostKey = get_host_key(Ssh0),
- H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, E, Public, K),
- H_SIG = sign_host_key(Ssh0, HostKey, H),
+ MyPrivHostKey = get_host_key(Ssh0),
+ MyPubHostKey = extract_public_key(MyPrivHostKey),
+ H = kex_h(Ssh0, MyPubHostKey, Min, NBits, Max, P, G, E, Public, K),
+ H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
{SshPacket, Ssh} =
- ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey,
+ ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey,
f = Public,
h_sig = H_SIG}, Ssh0),
{ok, SshPacket, Ssh#ssh{shared_secret = K,
@@ -482,7 +540,7 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E},
})
end.
-handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey,
+handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostKey,
f = F,
h_sig = H_SIG},
#ssh{keyex_key = {{Private, Public}, {G, P}},
@@ -494,9 +552,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey,
K = compute_key(dh, F, Private, [P,G]),
if
1<K, K<(P-1) ->
- H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, Public, F, K),
+ H = kex_h(Ssh0, PeerPubHostKey, Min, NBits, Max, P, G, Public, F, K),
- case verify_host_key(Ssh0, HostKey, H, H_SIG) of
+ case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
{ok, SshPacket, Ssh#ssh{shared_secret = K,
@@ -539,11 +597,12 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
true ->
{MyPublic, MyPrivate} = generate_key(ecdh, Curve),
K = compute_key(ecdh, PeerPublic, MyPrivate, Curve),
- HostKey = get_host_key(Ssh0),
- H = kex_h(Ssh0, Curve, HostKey, PeerPublic, MyPublic, K),
- H_SIG = sign_host_key(Ssh0, HostKey, H),
+ MyPrivHostKey = get_host_key(Ssh0),
+ MyPubHostKey = extract_public_key(MyPrivHostKey),
+ H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K),
+ H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
{SshPacket, Ssh1} =
- ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey,
+ ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey,
q_s = MyPublic,
h_sig = H_SIG},
Ssh0),
@@ -561,7 +620,7 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
})
end.
-handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey,
+handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
q_s = PeerPublic,
h_sig = H_SIG},
#ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0
@@ -570,8 +629,8 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey,
case ecdh_validate_public_key(PeerPublic, Curve) of
true ->
K = compute_key(ecdh, PeerPublic, MyPrivate, Curve),
- H = kex_h(Ssh0, Curve, HostKey, MyPublic, PeerPublic, K),
- case verify_host_key(Ssh0, HostKey, H, H_SIG) of
+ H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K),
+ case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
{ok, SshPacket, Ssh#ssh{shared_secret = K,
@@ -596,7 +655,61 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey,
end.
-ecdh_validate_public_key(_, _) -> true. % FIXME: Far too many false positives :)
+%%%----------------------------------------------------------------
+%%%
+%%% Standards for Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1
+%%% Section 3.2.2.1
+%%%
+
+ecdh_validate_public_key(Key, Curve) ->
+ case key_size(Curve) of
+ undefined ->
+ false;
+
+ Sz ->
+ case dec_key(Key, Sz) of
+ {ok,Q} ->
+ case crypto:ec_curve(Curve) of
+ {{prime_field,P}, {A, B, _Seed},
+ _P0Bin, _OrderBin, _CoFactorBin} ->
+ on_curve(Q, bin2int(A), bin2int(B), bin2int(P))
+ end;
+
+ {error,compressed_not_implemented} -> % Be a bit generous...
+ true;
+
+ _Error ->
+ false
+ end
+ end.
+
+
+on_curve({X,Y}, A, B, P) when 0 =< X,X =< (P-1),
+ 0 =< Y,Y =< (P-1) ->
+ %% Section 3.2.2.1, point 2
+ (Y*Y) rem P == (X*X*X + A*X + B) rem P;
+on_curve(_, _, _, _) ->
+ false.
+
+
+bin2int(B) ->
+ Sz = erlang:bit_size(B),
+ <<I:Sz/big-unsigned-integer>> = B,
+ I.
+
+key_size(secp256r1) -> 256;
+key_size(secp384r1) -> 384;
+key_size(secp521r1) -> 528; % Round 521 up to closest 8-bits.
+key_size(_) -> undefined.
+
+
+dec_key(Key, NBits) ->
+ Size = 8 + 2*NBits,
+ case <<Key:Size>> of
+ <<4:8, X:NBits, Y:NBits>> -> {ok,{X,Y}};
+ <<4:8, _/binary>> -> {error,bad_format};
+ _ -> {error,compressed_not_implemented}
+ end.
%%%----------------------------------------------------------------
handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
@@ -623,33 +736,49 @@ 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,OID}}) -> sha(OID);
+sign_host_key_sha(#'RSAPrivateKey'{}) -> sha;
+sign_host_key_sha(#'DSAPrivateKey'{}) -> sha.
+
+
+extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
+ #'RSAPublicKey'{modulus = N, publicExponent = E};
+extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->
+ {Y, #'Dss-Parms'{p=P, q=Q, g=G}};
+extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
+ publicKey = Q}) ->
+ {#'ECPoint'{point=Q}, {namedCurve,OID}}.
+
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'{},{namedCurve,OID}}) -> sha(OID).
+
+public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa';
+public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
+public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
+ Curve = public_key:oid2ssh_curvename(OID),
+ list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
+
accepted_host(Ssh, PeerName, Opts) ->
case proplists:get_value(silently_accept_hosts, Opts, false) of
@@ -830,11 +959,18 @@ ssh_packet(Msg, Ssh) ->
BinMsg = ssh_message:encode(Msg),
pack(BinMsg, Ssh).
+pack(Data, Ssh=#ssh{}) ->
+ pack(Data, Ssh, 0).
+
+%%% Note: pack/3 is only to be called from tests that wants
+%%% to deliberetly send packets with wrong PacketLength!
+%%% Use pack/2 for all other purposes!
pack(Data0, #ssh{encrypt_block_size = BlockSize,
send_sequence = SeqNum, send_mac = MacAlg,
send_mac_key = MacKey,
random_length_padding = RandomLengthPadding}
- = Ssh0) when is_binary(Data0) ->
+ = Ssh0,
+ PacketLenDeviationForTests) when is_binary(Data0) ->
{Ssh1, Data} = compress(Ssh0, Data0),
PL = (BlockSize - ((4 + 1 + size(Data)) rem BlockSize)) rem BlockSize,
MinPaddingLen = if PL < 4 -> PL + BlockSize;
@@ -847,7 +983,7 @@ pack(Data0, #ssh{encrypt_block_size = BlockSize,
end,
PaddingLen = MinPaddingLen + ExtraPaddingLen,
Padding = ssh_bits:random(PaddingLen),
- PacketLen = 1 + PaddingLen + size(Data),
+ PacketLen = 1 + PaddingLen + size(Data) + PacketLenDeviationForTests,
PacketData = <<?UINT32(PacketLen),?BYTE(PaddingLen),
Data/binary, Padding/binary>>,
{Ssh2, EncPacket} = encrypt(Ssh1, PacketData),
@@ -889,6 +1025,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 +1036,18 @@ 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'{},_} = Key) ->
+ <<?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, Key);
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 +1078,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 +1144,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 +1192,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 +1251,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 +1343,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
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1268,52 +1434,58 @@ hash(K, H, Ki, N, HASH) ->
hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HASH).
kex_h(SSH, Key, E, F, K) ->
+ KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version,
SSH#ssh.c_keyinit, SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), E,F,K],
+ KeyBin, E,F,K],
[string,string,binary,binary,binary,
mpint,mpint,mpint]),
crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
%% crypto:hash(sha,L).
kex_h(SSH, Curve, Key, Q_c, Q_s, K) ->
+ KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version,
SSH#ssh.c_keyinit, SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), Q_c, Q_s, K],
+ KeyBin, Q_c, Q_s, K],
[string,string,binary,binary,binary,
mpint,mpint,mpint]),
crypto:hash(sha(Curve), L).
kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) ->
L = if Min==-1; Max==-1 ->
+ KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
Ts = [string,string,binary,binary,binary,
uint32,
mpint,mpint,mpint,mpint,mpint],
ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), NBits, Prime, Gen, E,F,K],
+ KeyBin, NBits, Prime, Gen, E,F,K],
Ts);
true ->
+ KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
Ts = [string,string,binary,binary,binary,
uint32,uint32,uint32,
mpint,mpint,mpint,mpint,mpint],
ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
- ssh_message:encode_host_key(Key), Min, NBits, Max,
+ KeyBin, Min, NBits, Max,
Prime, Gen, E,F,K], Ts)
end,
crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
-sha('nistp256') -> sha256;
-sha('secp256r1')-> sha256;
-sha('nistp384') -> sha384;
-sha('secp384r1')-> sha384;
-sha('nistp521') -> sha512;
-sha('secp521r1')-> sha512;
+
+sha(secp256r1) -> sha256;
+sha(secp384r1) -> sha384;
+sha(secp521r1) -> sha512;
sha('diffie-hellman-group1-sha1') -> sha;
sha('diffie-hellman-group14-sha1') -> sha;
sha('diffie-hellman-group-exchange-sha1') -> sha;
-sha('diffie-hellman-group-exchange-sha256') -> sha256.
+sha('diffie-hellman-group-exchange-sha256') -> sha256;
+sha(?'secp256r1') -> sha(secp256r1);
+sha(?'secp384r1') -> sha(secp384r1);
+sha(?'secp521r1') -> sha(secp521r1).
+
mac_key_size('hmac-sha1') -> 20*8;
mac_key_size('hmac-sha1-96') -> 20*8;
@@ -1340,44 +1512,10 @@ peer_name({Host, _}) ->
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-dh_group('diffie-hellman-group1-sha1') -> element(2, ?dh_group1);
-dh_group('diffie-hellman-group14-sha1') -> element(2, ?dh_group14).
-
-dh_gex_default_groups() -> ?dh_default_groups.
-
-
-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} =
- lists:foldl(
- fun(_, {I,G}) when I==N ->
- %% If we have an exact match already: use that one
- {I,G};
- ({I,G}, _) when I==N ->
- %% If we now found an exact match: use that very one
- {I,G};
- ({I,G}, {Imax,_Gmax}) when Min=<I,I=<Max, % a) {I,G} fullfills the requirements
- I>Imax -> % b) {I,G} is larger than current max
- %% A group within the limits and better than the one we have
- {I,G};
- (_, IGmax) ->
- %% Keep the one we have
- IGmax
- end, {-1,undefined}, Groups),
-
- case Group of
- undefined ->
- throw(#ssh_msg_disconnect{
- code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "No possible diffie-hellman-group-exchange group found",
- language = ""});
- _ ->
- Group
- end.
-
+dh_group('diffie-hellman-group1-sha1') -> ?dh_group1;
+dh_group('diffie-hellman-group14-sha1') -> ?dh_group14.
+%%%----------------------------------------------------------------
generate_key(Algorithm, Args) ->
{Public,Private} = crypto:generate_key(Algorithm, Args),
{crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}.
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index 96ab1bb668..fd43326f0d 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).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -229,39 +229,13 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% rfc 2489, ch 6.2
+%%% Size 1024
-define(dh_group1,
- {1024,
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}}).
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}).
%%% rfc 3526, ch3
+%%% Size 2048
-define(dh_group14,
- {2048,
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}}).
-
-%%% rfc 3526, ch4
--define(dh_group15,
- {3072,
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF}}).
-
-%%% rfc 3526, ch5
--define(dh_group16,
- {4096,
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF}}).
-
-%%% rfc 3526, ch6
--define(dh_group17,
- {6144,
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF}}).
-
-%%% rfc 3526, ch7
--define(dh_group18,
- {8192,
- {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF}}).
-
--define(dh_default_groups, [?dh_group14,
- ?dh_group15,
- ?dh_group16,
- ?dh_group17,
- ?dh_group18] ).
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}).
-endif. % -ifdef(ssh_transport).
diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl
index e67fa2469f..85415a17de 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).
@@ -57,7 +58,7 @@ groups() ->
],
AlgoTcSet =
- [{Alg, [], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos)}
+ [{Alg, [parallel], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos)}
|| {Tag,Algs} <- ErlAlgos ++ DoubleAlgos,
Alg <- Algs],
@@ -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},
+ public_key:dh_gex_group_sizes(),
+ ?MAX_NUM_ALGORITHMS
+ ]),
ct:log("all() ->~n ~p.~n~ngroups()->~n ~p.~n",[all(),groups()]),
catch crypto:stop(),
case catch crypto:start() of
@@ -101,7 +110,8 @@ init_per_group(Group, Config) ->
Config;
false ->
%% An algorithm group
- [[{name,Tag}]|_] = ?config(tc_group_path, Config),
+ Tag = proplists:get_value(name,
+ hd(?config(tc_group_path, Config))),
Alg = Group,
PA =
case split(Alg) of
@@ -162,6 +172,52 @@ simple_exec(Config) ->
ssh_test_lib:std_simple_exec(Host, Port, Config).
%%--------------------------------------------------------------------
+%% Testing if no group matches
+simple_exec_groups_no_match_too_small(Config) ->
+ try simple_exec_group({400,500,600}, Config)
+ of
+ _ -> ct:fail("Exec though no group available")
+ catch
+ error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} ->
+ ok
+ end.
+
+simple_exec_groups_no_match_too_large(Config) ->
+ try simple_exec_group({9200,9500,9700}, Config)
+ of
+ _ -> ct:fail("Exec though no group available")
+ catch
+ error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+%% Testing all default groups
+simple_exec_groups(Config) ->
+ Sizes = interpolate( public_key:dh_gex_group_sizes() ),
+ lists:foreach(
+ fun(Sz) ->
+ ct:log("Try size ~p",[Sz]),
+ ct:comment(Sz),
+ case simple_exec_group(Sz, Config) of
+ expected -> ct:log("Size ~p ok",[Sz]);
+ _ -> ct:log("Size ~p not ok",[Sz])
+ end
+ end, Sizes),
+ ct:comment("~p",[lists:map(fun({_,I,_}) -> I;
+ (I) -> I
+ end,Sizes)]).
+
+
+interpolate([I1,I2|Is]) ->
+ OneThird = (I2-I1) div 3,
+ [I1,
+ {I1, I1 + OneThird, I2},
+ {I1, I1 + 2*OneThird, I2} | interpolate([I2|Is])];
+interpolate(Is) ->
+ Is.
+
+%%--------------------------------------------------------------------
%% Use the ssh client of the OS to connect
sshc_simple_exec(Config) ->
PrivDir = ?config(priv_dir, Config),
@@ -254,6 +310,16 @@ 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_groups,
+ simple_exec_groups_no_match_too_large,
+ simple_exec_groups_no_match_too_small
+ ];
+ _ ->
+ []
end.
supports(Tag, Alg, Algos) ->
@@ -295,3 +361,11 @@ setup_pubkey(Config) ->
ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
Config.
+
+simple_exec_group(I, Config) when is_integer(I) ->
+ simple_exec_group({I,I,I}, Config);
+simple_exec_group({Min,I,Max}, Config) ->
+ {Host,Port} = ?config(srvr_addr, Config),
+ ssh_test_lib:std_simple_exec(Host, Port, Config,
+ [{dh_gex_limits,{Min,I,Max}}]).
+
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 51431da48e..400edb4d2c 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]}
@@ -117,8 +123,6 @@ end_per_suite(_Config) ->
ssh:stop(),
crypto:stop().
%%--------------------------------------------------------------------
-init_per_group(hardening_tests, Config) ->
- init_per_group(dsa_key, Config);
init_per_group(dsa_key, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
@@ -129,6 +133,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),
@@ -190,8 +227,6 @@ init_per_group(dir_options, Config) ->
init_per_group(_, Config) ->
Config.
-end_per_group(hardening_tests, Config) ->
- end_per_group(dsa_key, Config);
end_per_group(dsa_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(PrivDir),
@@ -362,30 +397,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 +469,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 +499,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 +646,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 +680,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 +755,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 +878,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..1b93cc9c32 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,88 @@ 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),
+
+ %%%---- wait for the subsystem to terminate
+ receive
+ {ssh_cm,ConnectionRef,{closed,ChannelId0}} -> ok
+ after 5000 ->
+ ct:log("Timeout waiting for '{ssh_cm,~p,{closed,~p}}'~n"
+ "Message queue:~n~p",
+ [ConnectionRef,ChannelId0,erlang:process_info(self(),messages)]),
+ ct:fail("exit Timeout",[])
+ end,
+
+ %%%---- 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..6a201d401f 100644
--- a/lib/ssh/test/ssh_options_SUITE.erl
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -45,6 +45,9 @@
max_sessions_ssh_connect_sequential/1,
server_password_option/1,
server_userpassword_option/1,
+ server_pwdfun_option/1,
+ server_pwdfun_4_option/1,
+ server_pwdfun_4_option_repeat/1,
ssh_connect_arg4_timeout/1,
ssh_connect_negtimeout_parallel/1,
ssh_connect_negtimeout_sequential/1,
@@ -83,6 +86,9 @@ all() ->
connectfun_disconnectfun_client,
server_password_option,
server_userpassword_option,
+ server_pwdfun_option,
+ server_pwdfun_4_option,
+ server_pwdfun_4_option_repeat,
{group, dir_options},
ssh_connect_timeout,
ssh_connect_arg4_timeout,
@@ -188,7 +194,9 @@ init_per_testcase(_TestCase, Config) ->
Config.
end_per_testcase(TestCase, Config) when TestCase == server_password_option;
- TestCase == server_userpassword_option ->
+ TestCase == server_userpassword_option;
+ TestCase == server_pwdfun_option;
+ TestCase == server_pwdfun_4_option ->
UserDir = filename:join(?config(priv_dir, Config), nopubkey),
ssh_test_lib:del_dirs(UserDir),
end_per_testcase(Config);
@@ -272,6 +280,157 @@ server_userpassword_option(Config) when is_list(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+%%% validate to server that uses the 'pwdfun' option
+server_pwdfun_option(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),
+ CHKPWD = fun("foo",Pwd) -> Pwd=="bar";
+ (_,_) -> false
+ end,
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {pwdfun,CHKPWD}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "bar"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ ssh:close(ConnectionRef),
+
+ Reason = "Unable to connect using the available authentication methods",
+
+ {error, Reason} =
+ ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ {error, Reason} =
+ ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "vego"},
+ {password, "foo"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ ssh:stop_daemon(Pid).
+
+
+%%--------------------------------------------------------------------
+%%% validate to server that uses the 'pwdfun/4' option
+server_pwdfun_4_option(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),
+ PWDFUN = fun("foo",Pwd,{_,_},undefined) -> Pwd=="bar";
+ ("fie",Pwd,{_,_},undefined) -> {Pwd=="bar",new_state};
+ ("bandit",_,_,_) -> disconnect;
+ (_,_,_,_) -> false
+ end,
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {pwdfun,PWDFUN}]),
+ ConnectionRef1 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "bar"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ ssh:close(ConnectionRef1),
+
+ ConnectionRef2 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "fie"},
+ {password, "bar"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ ssh:close(ConnectionRef2),
+
+ Reason = "Unable to connect using the available authentication methods",
+
+ {error, Reason} =
+ ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ {error, Reason} =
+ ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "fie"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ {error, Reason} =
+ ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "vego"},
+ {password, "foo"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ {error, Reason} =
+ ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "bandit"},
+ {password, "pwd breaking"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ ssh:stop_daemon(Pid).
+
+
+%%--------------------------------------------------------------------
+server_pwdfun_4_option_repeat(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),
+ %% Test that the state works
+ Parent = self(),
+ PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true;
+ (_,P,_,S=undefined) -> Parent!{P,S},{false,1};
+ (_,P,_,S) -> Parent!{P,S}, {false,S+1}
+ end,
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {auth_methods,"keyboard-interactive"},
+ {pwdfun,PWDFUN}]),
+
+ %% Try with passwords "incorrect", "Bad again" and finally "bar"
+ KIFFUN = fun(_,_,_) ->
+ K={k,self()},
+ case get(K) of
+ undefined ->
+ put(K,1),
+ ["incorrect"];
+ 2 ->
+ put(K,3),
+ ["bar"];
+ S->
+ put(K,S+1),
+ ["Bad again"]
+ end
+ end,
+
+ ConnectionRef2 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {keyboard_interact_fun, KIFFUN},
+ {user_dir, UserDir}]),
+ ssh:close(ConnectionRef2),
+ ssh:stop_daemon(Pid),
+
+ lists:foreach(fun(Expect) ->
+ receive
+ Expect -> ok;
+ Other -> ct:fail("Expect: ~p~nReceived ~p",[Expect,Other])
+ after
+ 2000 -> ct:fail("Timeout expecting ~p",[Expect])
+ end
+ end, [{"incorrect",undefined},
+ {"Bad again",1},
+ {"bar",2}]).
+
+%%--------------------------------------------------------------------
system_dir_option(Config) ->
DirUnread = proplists:get_value(unreadable_dir,Config),
FileRead = proplists:get_value(readable_file,Config),
@@ -656,6 +815,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 +1022,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 +1032,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 +1051,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 +1179,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..3a7f47c2dd 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -46,7 +46,10 @@ suite() ->
all() ->
[{group,tool_tests},
- {group,kex}
+ {group,kex},
+ {group,service_requests},
+ {group,packet_size_error},
+ {group,field_size_error}
].
groups() ->
@@ -55,13 +58,25 @@ groups() ->
lib_match,
lib_no_match
]},
+ {packet_size_error, [], [packet_length_too_large,
+ packet_length_too_short]},
+
+ {field_size_error, [], [service_name_length_too_large,
+ service_name_length_too_short]},
+
{kex, [], [no_common_alg_server_disconnects,
no_common_alg_client_disconnects,
- gex_client_init_default_noexact,
- gex_client_init_default_exact,
gex_client_init_option_groups,
+ gex_server_gex_limit,
+ gex_client_init_option_groups_moduli_file,
gex_client_init_option_groups_file
- ]}
+ ]},
+ {service_requests, [], [bad_service_name,
+ bad_long_service_name,
+ bad_very_long_service_name,
+ empty_service_name,
+ bad_service_name_then_correct
+ ]}
].
@@ -76,10 +91,10 @@ end_per_suite(Config) ->
init_per_testcase(no_common_alg_server_disconnects, Config) ->
start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']}]}]);
-init_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
- TC == gex_client_init_default_exact ;
- TC == gex_client_init_option_groups ;
- TC == gex_client_init_option_groups_file ->
+init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
+ TC == gex_client_init_option_groups_moduli_file ;
+ TC == gex_client_init_option_groups_file ;
+ TC == gex_server_gex_limit ->
Opts = case TC of
gex_client_init_option_groups ->
[{dh_gex_groups, [{2345, 3, 41}]}];
@@ -87,21 +102,31 @@ init_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
DataDir = ?config(data_dir, Config),
F = filename:join(DataDir, "dh_group_test"),
[{dh_gex_groups, {file,F}}];
+ gex_client_init_option_groups_moduli_file ->
+ DataDir = ?config(data_dir, Config),
+ F = filename:join(DataDir, "dh_group_test.moduli"),
+ [{dh_gex_groups, {ssh_moduli_file,F}}];
+ gex_server_gex_limit ->
+ [{dh_gex_groups, [{ 500, 3, 18},
+ {1000, 7, 91},
+ {3000, 5, 61}]},
+ {dh_gex_limits,{500,1500}}
+ ];
_ ->
[]
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).
end_per_testcase(no_common_alg_server_disconnects, Config) ->
stop_std_daemon(Config);
-end_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
- TC == gex_client_init_default_exact ;
- TC == gex_client_init_option_groups ;
- TC == gex_client_init_option_groups_file ->
+end_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
+ TC == gex_client_init_option_groups_moduli_file ;
+ TC == gex_client_init_option_groups_file ;
+ TC == gex_server_gex_limit ->
stop_std_daemon(Config);
end_per_testcase(_TestCase, Config) ->
check_std_daemon_works(Config, ?LINE).
@@ -114,25 +139,10 @@ end_per_testcase(_TestCase, Config) ->
%%% Connect to an erlang server and check that the testlib acts as a client.
lib_works_as_client(Config) ->
%% Connect and negotiate keys
- {ok,InitialState} =
- ssh_trpt_test_lib:exec(
- [{set_options, [print_ops, print_seqnums, print_messages]},
- {connect,
- server_host(Config),server_port(Config),
- [{preferred_algorithms,[{kex,['diffie-hellman-group1-sha1']}]},
- {silently_accept_hosts, true},
- {user_dir, user_dir(Config)},
- {user_interaction, false}]},
- receive_hello,
- {send, hello},
- {send, ssh_msg_kexinit},
- {match, #ssh_msg_kexinit{_='_'}, receive_msg},
- {send, ssh_msg_kexdh_init},
- {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
- {send, #ssh_msg_newkeys{}},
- {match, #ssh_msg_newkeys{_='_'}, receive_msg}
- ]
- ),
+ {ok,InitialState} = ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]}]
+ ),
+ {ok,AfterKexState} = connect_and_kex(Config, InitialState),
%% Do the authentcation
{User,Pwd} = server_user_password(Config),
@@ -147,7 +157,7 @@ lib_works_as_client(Config) ->
?STRING(unicode:characters_to_binary(Pwd))>>
}},
{match, #ssh_msg_userauth_success{_='_'}, receive_msg}
- ], InitialState),
+ ], AfterKexState),
%% Disconnect
{ok,_} =
@@ -327,31 +337,29 @@ no_common_alg_client_disconnects(Config) ->
X ->
ct:log("¤¤¤¤¤"),
ct:fail(X)
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
%%%--------------------------------------------------------------------
-gex_client_init_default_noexact(Config) ->
- do_gex_client_init(Config, {2000, 3000, 4000},
- %% Warning, app knowledege:
- ?dh_group15).
-
-
-gex_client_init_default_exact(Config) ->
- do_gex_client_init(Config, {2000, 2048, 4000},
- %% Warning, app knowledege:
- ?dh_group14).
-
-
gex_client_init_option_groups(Config) ->
do_gex_client_init(Config, {2000, 2048, 4000},
- {'n/a',{3,41}}).
-
+ {3,41}).
gex_client_init_option_groups_file(Config) ->
do_gex_client_init(Config, {2000, 2048, 4000},
- {'n/a',{5,61}}).
+ {5,61}).
+
+gex_client_init_option_groups_moduli_file(Config) ->
+ do_gex_client_init(Config, {2000, 2048, 4000},
+ {5,16#B7}).
-do_gex_client_init(Config, {Min,N,Max}, {_,{G,P}}) ->
+gex_server_gex_limit(Config) ->
+ do_gex_client_init(Config, {1000, 3000, 4000},
+ {7,91}).
+
+
+do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
{ok,_} =
ssh_trpt_test_lib:exec(
[{set_options, [print_ops, print_seqnums, print_messages]},
@@ -373,6 +381,106 @@ do_gex_client_init(Config, {Min,N,Max}, {_,{G,P}}) ->
]
).
+
+%%%--------------------------------------------------------------------
+bad_service_name(Config) ->
+ bad_service_name(Config, "kfglkjf").
+
+bad_long_service_name(Config) ->
+ bad_service_name(Config,
+ lists:duplicate(?SSH_MAX_PACKET_SIZE div 2, $a)).
+
+bad_very_long_service_name(Config) ->
+ bad_service_name(Config,
+ lists:duplicate(4*?SSH_MAX_PACKET_SIZE, $a)).
+
+empty_service_name(Config) ->
+ bad_service_name(Config, "").
+
+bad_service_name_then_correct(Config) ->
+ {ok,InitialState} = connect_and_kex(Config),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]},
+ {send, #ssh_msg_service_request{name = "kdjglkfdjgkldfjglkdfjglkfdjglkj"}},
+ {send, #ssh_msg_service_request{name = "ssh-connection"}},
+ {match, {'or',[#ssh_msg_disconnect{_='_'},
+ tcp_closed
+ ]},
+ receive_msg}
+ ], InitialState).
+
+
+bad_service_name(Config, Name) ->
+ {ok,InitialState} = connect_and_kex(Config),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]},
+ {send, #ssh_msg_service_request{name = Name}},
+ {match, {'or',[#ssh_msg_disconnect{_='_'},
+ tcp_closed
+ ]},
+ receive_msg}
+ ], InitialState).
+
+%%%--------------------------------------------------------------------
+packet_length_too_large(Config) -> bad_packet_length(Config, +4).
+
+packet_length_too_short(Config) -> bad_packet_length(Config, -4).
+
+bad_packet_length(Config, LengthExcess) ->
+ PacketFun =
+ fun(Msg, Ssh) ->
+ BinMsg = ssh_message:encode(Msg),
+ ssh_transport:pack(BinMsg, Ssh, LengthExcess)
+ end,
+ {ok,InitialState} = connect_and_kex(Config),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]},
+ {send, {special,
+ #ssh_msg_service_request{name="ssh-userauth"},
+ PacketFun}},
+ %% Prohibit remote decoder starvation:
+ {send, #ssh_msg_service_request{name="ssh-userauth"}},
+ {match, {'or',[#ssh_msg_disconnect{_='_'},
+ tcp_closed
+ ]},
+ receive_msg}
+ ], InitialState).
+
+%%%--------------------------------------------------------------------
+service_name_length_too_large(Config) -> bad_service_name_length(Config, +4).
+
+service_name_length_too_short(Config) -> bad_service_name_length(Config, -4).
+
+
+bad_service_name_length(Config, LengthExcess) ->
+ PacketFun =
+ fun(#ssh_msg_service_request{name=Service}, Ssh) ->
+ BinName = list_to_binary(Service),
+ BinMsg =
+ <<?BYTE(?SSH_MSG_SERVICE_REQUEST),
+ %% A bad string encoding of Service:
+ ?UINT32(size(BinName)+LengthExcess), BinName/binary
+ >>,
+ ssh_transport:pack(BinMsg, Ssh)
+ end,
+ {ok,InitialState} = connect_and_kex(Config),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]},
+ {send, {special,
+ #ssh_msg_service_request{name="ssh-userauth"},
+ PacketFun} },
+ %% Prohibit remote decoder starvation:
+ {send, #ssh_msg_service_request{name="ssh-userauth"}},
+ {match, {'or',[#ssh_msg_disconnect{_='_'},
+ tcp_closed
+ ]},
+ receive_msg}
+ ], InitialState).
+
%%%================================================================
%%%==== Internal functions ========================================
%%%================================================================
@@ -480,3 +588,24 @@ std_connect(Host, Port, Config, Opts) ->
30000).
%%%----------------------------------------------------------------
+connect_and_kex(Config) ->
+ connect_and_kex(Config, ssh_trpt_test_lib:exec([]) ).
+
+connect_and_kex(Config, InitialState) ->
+ ssh_trpt_test_lib:exec(
+ [{connect,
+ server_host(Config),server_port(Config),
+ [{preferred_algorithms,[{kex,['diffie-hellman-group1-sha1']}]},
+ {silently_accept_hosts, true},
+ {user_dir, user_dir(Config)},
+ {user_interaction, false}]},
+ receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_init},
+ {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
+ {send, #ssh_msg_newkeys{}},
+ {match, #ssh_msg_newkeys{_='_'}, receive_msg}
+ ],
+ InitialState).
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli
new file mode 100644
index 0000000000..f6995ba4c9
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli
@@ -0,0 +1,3 @@
+20151021104105 2 6 100 2222 5 B7
+20151021104106 2 6 100 1111 5 4F
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl
index 9daa6efc02..ef631d54bd 100644
--- a/lib/ssh/test/ssh_renegotiate_SUITE.erl
+++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl
@@ -89,9 +89,10 @@ rekey_limit(Config) ->
UserDir = ?config(priv_dir, Config),
DataFile = filename:join(UserDir, "rekey.data"),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[]),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, 4500}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, 6000},
+ {max_random_length_padding,0}]),
{ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
Kex1 = get_kex_init(ConnectionRef),
@@ -132,13 +133,13 @@ renegotiate1(Config) ->
UserDir = ?config(priv_dir, Config),
DataFile = filename:join(UserDir, "renegotiate1.data"),
- {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[]),
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}]),
RPort = ssh_test_lib:inet_port(),
{ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, []),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
{ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
Kex1 = get_kex_init(ConnectionRef),
@@ -170,12 +171,12 @@ renegotiate2(Config) ->
UserDir = ?config(priv_dir, Config),
DataFile = filename:join(UserDir, "renegotiate2.data"),
- {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[]),
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}]),
RPort = ssh_test_lib:inet_port(),
{ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, []),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
{ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
Kex1 = get_kex_init(ConnectionRef),
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..5816b708f2 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,14 @@ 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 = Param = {namedCurve,_Id0}} = ECDSA,
+ PKey = #'ECPoint'{point = Q},
+ setup_auth_keys([{ {PKey,Param}, [{comment, "Test"}]}], UserDir).
+
setup_auth_keys(Keys, Dir) ->
AuthKeys = public_key:ssh_encode(Keys, auth_keys),
AuthKeysFile = filename:join(Dir, "authorized_keys"),
@@ -424,6 +459,14 @@ openssh_sanity_check(Config) ->
{skip, Str}
end.
+openssh_supports(ClientOrServer, Tag, Alg) when ClientOrServer == sshc ;
+ ClientOrServer == sshd ->
+ SSH_algos = ssh_test_lib:default_algorithms(ClientOrServer),
+ L = proplists:get_value(Tag, SSH_algos, []),
+ lists:member(Alg, L) orelse
+ lists:member(Alg, proplists:get_value(client2server, L, [])) orelse
+ lists:member(Alg, proplists:get_value(server2client, L, [])).
+
%%--------------------------------------------------------------------
%% Check if we have a "newer" ssh client that supports these test cases
@@ -443,7 +486,63 @@ check_ssh_client_support2(P) ->
-1
end.
-default_algorithms(Host, Port) ->
+%%%--------------------------------------------------------------------
+%%% Probe a server or a client about algorithm support
+
+default_algorithms(sshd) ->
+ default_algorithms(sshd, "localhost", 22);
+
+default_algorithms(sshc) ->
+ default_algorithms(sshc, []).
+
+default_algorithms(sshd, Host, Port) ->
+ try run_fake_ssh(
+ ssh_trpt_test_lib:exec(
+ [{connect,Host,Port, [{silently_accept_hosts, true},
+ {user_interaction, false}]}]))
+ catch
+ _C:_E ->
+ ct:pal("***~p:~p: ~p:~p",[?MODULE,?LINE,_C,_E]),
+ []
+ end.
+
+default_algorithms(sshc, DaemonOptions) ->
+ Parent = self(),
+ %% Start a process handling one connection on the server side:
+ Srvr =
+ spawn_link(
+ fun() ->
+ Parent !
+ {result, self(),
+ try
+ {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
+ Parent ! {hostport,self(),ssh_trpt_test_lib:server_host_port(InitialState)},
+ run_fake_ssh(
+ ssh_trpt_test_lib:exec([{accept, DaemonOptions}],
+ InitialState))
+ catch
+ _C:_E ->
+ ct:pal("***~p:~p: ~p:~p",[?MODULE,?LINE,_C,_E]),
+ []
+ end}
+ end),
+
+ receive
+ {hostport,Srvr,{_Host,Port}} ->
+ spawn(fun()-> os:cmd(lists:concat(["ssh -o \"StrictHostKeyChecking no\" -p ",Port," localhost"])) end)
+ after ?TIMEOUT ->
+ ct:fail("No server respons 1")
+ end,
+
+ receive
+ {result,Srvr,L} ->
+ L
+ after ?TIMEOUT ->
+ ct:fail("No server respons 2")
+ end.
+
+
+run_fake_ssh({ok,InitialState}) ->
KexInitPattern =
#ssh_msg_kexinit{
kex_algorithms = '$kex_algorithms',
@@ -456,61 +555,35 @@ default_algorithms(Host, Port) ->
compression_algorithms_server_to_client = '$compression_algorithms_server_to_client',
_ = '_'
},
+ {ok,E} = ssh_trpt_test_lib:exec([{set_options,[silent]},
+ {send, hello},
+ receive_hello,
+ {send, ssh_msg_kexinit},
+ {match, KexInitPattern, receive_msg},
+ close_socket
+ ],
+ InitialState),
+ [Kex, PubKey, EncC2S, EncS2C, MacC2S, MacS2C, CompC2S, CompS2C] =
+ ssh_trpt_test_lib:instantiate(['$kex_algorithms',
+ '$server_host_key_algorithms',
+ '$encryption_algorithms_client_to_server',
+ '$encryption_algorithms_server_to_client',
+ '$mac_algorithms_client_to_server',
+ '$mac_algorithms_server_to_client',
+ '$compression_algorithms_client_to_server',
+ '$compression_algorithms_server_to_client'
+ ], E),
+ [{kex, to_atoms(Kex)},
+ {public_key, to_atoms(PubKey)},
+ {cipher, [{client2server, to_atoms(EncC2S)},
+ {server2client, to_atoms(EncS2C)}]},
+ {mac, [{client2server, to_atoms(MacC2S)},
+ {server2client, to_atoms(MacS2C)}]},
+ {compression, [{client2server, to_atoms(CompC2S)},
+ {server2client, to_atoms(CompS2C)}]}].
+
- try ssh_trpt_test_lib:exec(
- [{connect,Host,Port, [{silently_accept_hosts, true},
- {user_interaction, false}]},
- {send,hello},
- receive_hello,
- {send, ssh_msg_kexinit},
- {match, KexInitPattern, receive_msg},
- close_socket])
- of
- {ok,E} ->
- [Kex, PubKey, EncC2S, EncS2C, MacC2S, MacS2C, CompC2S, CompS2C] =
- ssh_trpt_test_lib:instantiate(['$kex_algorithms',
- '$server_host_key_algorithms',
- '$encryption_algorithms_client_to_server',
- '$encryption_algorithms_server_to_client',
- '$mac_algorithms_client_to_server',
- '$mac_algorithms_server_to_client',
- '$compression_algorithms_client_to_server',
- '$compression_algorithms_server_to_client'
- ], E),
- [{kex, to_atoms(Kex)},
- {public_key, to_atoms(PubKey)},
- {cipher, [{client2server, to_atoms(EncC2S)},
- {server2client, to_atoms(EncS2C)}]},
- {mac, [{client2server, to_atoms(MacC2S)},
- {server2client, to_atoms(MacS2C)}]},
- {compression, [{client2server, to_atoms(CompC2S)},
- {server2client, to_atoms(CompS2C)}]}];
- _ ->
- []
- catch
- _:_ ->
- []
- end.
-
-
-default_algorithms(sshd) ->
- default_algorithms("localhost", 22);
-default_algorithms(sshc) ->
- case os:find_executable("ssh") of
- false ->
- [];
- _ ->
- Cipher = sshc(cipher),
- Mac = sshc(mac),
- [{kex, sshc(kex)},
- {public_key, sshc(key)},
- {cipher, [{client2server, Cipher},
- {server2client, Cipher}]},
- {mac, [{client2server, Mac},
- {server2client, Mac}]}
- ]
- end.
-
+%%--------------------------------------------------------------------
sshc(Tag) ->
to_atoms(
string:tokens(os:cmd(lists:concat(["ssh -Q ",Tag])), "\n")
@@ -552,4 +625,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..d1dfa2efdf 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -45,7 +45,6 @@ all() ->
groups() ->
[{erlang_client, [], [erlang_shell_client_openssh_server,
- erlang_client_openssh_server_exec,
erlang_client_openssh_server_exec_compressed,
erlang_client_openssh_server_setenv,
erlang_client_openssh_server_publickey_rsa,
@@ -54,12 +53,7 @@ groups() ->
erlang_client_openssh_server_kexs,
erlang_client_openssh_server_nonexistent_subsystem
]},
- {erlang_server, [], [erlang_server_openssh_client_exec,
- erlang_server_openssh_client_exec_compressed,
- erlang_server_openssh_client_pulic_key_dsa,
- erlang_server_openssh_client_cipher_suites,
- erlang_server_openssh_client_macs,
- erlang_server_openssh_client_kexs]}
+ {erlang_server, [], [erlang_server_openssh_client_public_key_dsa]}
].
init_per_suite(Config) ->
@@ -88,7 +82,7 @@ init_per_group(erlang_server, Config) ->
init_per_group(erlang_client, Config) ->
CommonAlgs = ssh_test_lib:algo_intersection(
ssh:default_algorithms(),
- ssh_test_lib:default_algorithms("localhost", 22)),
+ ssh_test_lib:default_algorithms(sshd)),
[{common_algs,CommonAlgs} | Config];
init_per_group(_, Config) ->
Config.
@@ -100,18 +94,21 @@ end_per_group(erlang_server, Config) ->
end_per_group(_, Config) ->
Config.
-init_per_testcase(erlang_server_openssh_client_cipher_suites, Config) ->
- check_ssh_client_support(Config);
-
-init_per_testcase(erlang_server_openssh_client_macs, Config) ->
- check_ssh_client_support(Config);
-
-init_per_testcase(erlang_server_openssh_client_kexs, Config) ->
- check_ssh_client_support(Config);
-
-init_per_testcase(erlang_client_openssh_server_kexs, Config) ->
- check_ssh_client_support(Config);
+init_per_testcase(erlang_server_openssh_client_public_key_dsa, Config) ->
+ case ssh_test_lib:openssh_supports(sshc, public_key, 'ssh-dss') of
+ true ->
+ init_per_testcase('__default__',Config);
+ false ->
+ {skip,"openssh client does not support DSA"}
+ end;
+init_per_testcase(erlang_client_openssh_server_publickey_dsa, Config) ->
+ case ssh_test_lib:openssh_supports(sshd, public_key, 'ssh-dss') of
+ true ->
+ init_per_testcase('__default__',Config);
+ false ->
+ {skip,"openssh client does not support DSA"}
+ end;
init_per_testcase(_TestCase, Config) ->
ssh:start(),
Config.
@@ -182,23 +179,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.
%%--------------------------------------------------------------------
@@ -252,202 +255,6 @@ erlang_client_openssh_server_kexs(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-erlang_server_openssh_client_exec() ->
- [{doc, "Test that exec command works."}].
-
-erlang_server_openssh_client_exec(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
-
- ct:sleep(500),
-
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " 1+1.",
-
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
-
- end,
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_cipher_suites() ->
- [{doc, "Test that we can connect with different cipher suites."}].
-
-erlang_server_openssh_client_cipher_suites(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
- ct:sleep(500),
-
- OpenSshCiphers =
- ssh_test_lib:to_atoms(
- string:tokens(os:cmd("ssh -Q cipher"), "\n")),
- ErlCiphers =
- proplists:get_value(client2server,
- proplists:get_value(cipher, ssh:default_algorithms())),
- CommonCiphers =
- ssh_test_lib:algo_intersection(ErlCiphers, OpenSshCiphers),
-
- comment(CommonCiphers),
-
- lists:foreach(
- fun(Cipher) ->
- Cmd = lists:concat(["ssh -p ",Port,
- " -o UserKnownHostsFile=",KnownHosts," ",Host," ",
- " -c ",Cipher," 1+1."]),
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("~p Did not receive answer",[Cipher])
- end
- end, CommonCiphers),
-
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_macs() ->
- [{doc, "Test that we can connect with different MACs."}].
-
-erlang_server_openssh_client_macs(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
-
- ct:sleep(500),
-
- OpenSshMacs =
- ssh_test_lib:to_atoms(
- string:tokens(os:cmd("ssh -Q mac"), "\n")),
- ErlMacs =
- proplists:get_value(client2server,
- proplists:get_value(mac, ssh:default_algorithms())),
- CommonMacs =
- ssh_test_lib:algo_intersection(ErlMacs, OpenSshMacs),
-
- comment(CommonMacs),
-
- lists:foreach(
- fun(MAC) ->
- Cmd = lists:concat(["ssh -p ",Port,
- " -o UserKnownHostsFile=",KnownHosts," ",Host," ",
- " -o MACs=",MAC," 1+1."]),
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("~p Did not receive answer",[MAC])
- end
- end, CommonMacs),
-
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_kexs() ->
- [{doc, "Test that we can connect with different KEXs."}].
-
-erlang_server_openssh_client_kexs(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
-
- {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)}]}
- ]),
- ct:sleep(500),
-
- OpenSshKexs =
- ssh_test_lib:to_atoms(
- string:tokens(os:cmd("ssh -Q kex"), "\n")),
- ErlKexs =
- proplists:get_value(kex, ssh:default_algorithms()),
- CommonKexs =
- ssh_test_lib:algo_intersection(ErlKexs, OpenSshKexs),
-
- comment(CommonKexs),
-
- lists:foreach(
- fun(Kex) ->
- Cmd = lists:concat(["ssh -p ",Port,
- " -o UserKnownHostsFile=",KnownHosts," ",Host," ",
- " -o KexAlgorithms=",Kex," 1+1."]),
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:log("~p Did not receive answer",[Kex])
- end
- end, CommonKexs),
-
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-erlang_server_openssh_client_exec_compressed() ->
- [{doc, "Test that exec command works."}].
-
-erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
- SystemDir = ?config(data_dir, 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}]),
-
- ct:sleep(500),
-
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " -C "++ Host ++ " 1+1.",
- SshPort = open_port({spawn, Cmd}, [binary]),
-
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
-
- end,
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
erlang_client_openssh_server_setenv() ->
[{doc, "Test api function ssh_connection:setenv"}].
@@ -543,9 +350,9 @@ erlang_client_openssh_server_publickey_dsa(Config) when is_list(Config) ->
{skip, "no ~/.ssh/id_dsa"}
end.
%%--------------------------------------------------------------------
-erlang_server_openssh_client_pulic_key_dsa() ->
+erlang_server_openssh_client_public_key_dsa() ->
[{doc, "Validate using dsa publickey."}].
-erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) ->
+erlang_server_openssh_client_public_key_dsa(Config) when is_list(Config) ->
SystemDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
KnownHosts = filename:join(PrivDir, "known_hosts"),
@@ -642,6 +449,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 +460,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 +478,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() ->
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index caf9bac3b6..4269529ae8 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -73,7 +73,10 @@ exec(Op, S0=#s{}) ->
op(Op, S1))
of
S = #s{} ->
- print_traces(S),
+ case proplists:get_value(silent,S#s.opts) of
+ true -> ok;
+ _ -> print_traces(S)
+ end,
{ok,S}
catch
{fail,Reason,Se} ->
@@ -383,7 +386,14 @@ send(S0, Line) when is_binary(Line) ->
fun(X) when X==true;X==detail -> {"Send line~n~p~n",[Line]} end),
send_bytes(Line, S#s{return_value = Line});
-%%% Msg = #ssh_msg_*{}
+send(S0, {special,Msg,PacketFun}) when is_tuple(Msg),
+ is_function(PacketFun,2) ->
+ S = opt(print_messages, S0,
+ fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
+ {Packet, C} = PacketFun(Msg, S#s.ssh),
+ send_bytes(Packet, S#s{ssh = C, %%inc_send_seq_num(C),
+ return_value = Msg});
+
send(S0, Msg) when is_tuple(Msg) ->
S = opt(print_messages, S0,
fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
@@ -743,7 +753,7 @@ print_traces(S) ->
[case Len-length(Acc)-1 of
0 ->
io_lib:format(Fmt,Args);
- N ->
+ _N ->
io_lib:format(lists:concat(['~p --------~n',Fmt]),
[Len-length(Acc)-1|Args])
end | Acc]