aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/doc/src/ssh.xml94
-rw-r--r--lib/ssh/src/ssh.erl117
-rw-r--r--lib/ssh/src/ssh.hrl1
-rw-r--r--lib/ssh/src/ssh_auth.erl60
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl18
-rw-r--r--lib/ssh/src/ssh_file.erl16
-rw-r--r--lib/ssh/src/ssh_transport.erl176
-rw-r--r--lib/ssh/src/ssh_transport.hrl35
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE.erl74
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl161
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl58
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli3
12 files changed, 570 insertions, 243 deletions
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 2b190c98b6..0e5a0706f5 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -61,6 +61,29 @@
</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
@@ -462,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>
+ <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[{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>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.
+ <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>
+ <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>
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 049018b21c..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()}.
@@ -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]);
@@ -420,27 +423,67 @@ 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;
@@ -462,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),
@@ -660,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 fc9d60c500..4ad936f742 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -138,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 04749fcf8e..4967a2e4cd 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -174,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,
@@ -335,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;
@@ -364,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).
@@ -387,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) ->
@@ -451,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);
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 099268d521..8448218d91 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -627,14 +627,24 @@ userauth_keyboard_interactive(#ssh_msg_userauth_info_response{} = Msg,
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).
%%--------------------------------------------------------------------
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index c087ce14d7..2f16a31cba 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -221,9 +221,11 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
{ok, Fd} ->
Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg),
file:close(Fd),
- {ok, Res};
- {error, enoent} -> {error, not_found};
- Error -> Error
+ Res;
+ {error, enoent} ->
+ {error, not_found};
+ Error ->
+ Error
end.
identity_key_filename('ssh-dss' ) -> "id_dsa";
@@ -242,6 +244,9 @@ lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) ->
case io:get_line(Fd, '') of
eof ->
{error, not_found};
+ {error,Error} ->
+ %% Rare... For example NFS errors
+ {error,Error};
Line ->
case ssh_decode_line(Line, known_hosts) of
[{Key, Attributes}] ->
@@ -262,7 +267,7 @@ handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) ->
Host1 = host_name(Host),
case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of
true when KeyToMatch == Key ->
- Key;
+ {ok,Key};
_ ->
lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType)
end.
@@ -309,6 +314,9 @@ lookup_user_key_fd(Fd, Key) ->
case io:get_line(Fd, '') of
eof ->
{error, not_found};
+ {error,Error} ->
+ %% Rare... For example NFS errors
+ {error,Error};
Line ->
case ssh_decode_line(Line, auth_keys) of
[{AuthKey, _}] ->
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index d622ec27fc..0c999b96cc 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -441,19 +441,29 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey,
%%%
%%% 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{
@@ -462,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]),
@@ -563,10 +593,11 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) ->
%% at server
Curve = ecdh_curve(Kex),
- case ecdh_validate_public_key(PeerPublic, Curve) of
- true ->
- {MyPublic, MyPrivate} = generate_key(ecdh, Curve),
- K = compute_key(ecdh, PeerPublic, MyPrivate, Curve),
+ {MyPublic, MyPrivate} = generate_key(ecdh, Curve),
+ try
+ compute_key(ecdh, PeerPublic, MyPrivate, Curve)
+ of
+ K ->
MyPrivHostKey = get_host_key(Ssh0),
MyPubHostKey = extract_public_key(MyPrivHostKey),
H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K),
@@ -579,9 +610,9 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
{ok, SshPacket, Ssh1#ssh{keyex_key = {{MyPublic,MyPrivate},Curve},
shared_secret = K,
exchanged_hash = H,
- session_id = sid(Ssh1, H)}};
-
- false ->
+ session_id = sid(Ssh1, H)}}
+ catch
+ _:_ ->
throw({{error,invalid_peer_public_key},
#ssh_msg_disconnect{
code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
@@ -596,9 +627,10 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
#ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0
) ->
%% at client
- case ecdh_validate_public_key(PeerPublic, Curve) of
- true ->
- K = compute_key(ecdh, PeerPublic, MyPrivate, Curve),
+ try
+ compute_key(ecdh, PeerPublic, MyPrivate, Curve)
+ of
+ K ->
H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
@@ -613,9 +645,9 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
description = "Key exchange failed",
language = ""}
})
- end;
-
- false ->
+ end
+ catch
+ _:_ ->
throw({{error,invalid_peer_public_key},
#ssh_msg_disconnect{
code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
@@ -626,62 +658,6 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
%%%----------------------------------------------------------------
-%%%
-%%% 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) ->
try install_alg(Ssh0) of
#ssh{} = Ssh ->
@@ -1482,44 +1458,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.
- {_Size,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 337f455279..fd43326f0d 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -229,40 +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_group1,
- ?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 2ab83d84e1..85415a17de 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE.erl
+++ b/lib/ssh/test/ssh_algorithms_SUITE.erl
@@ -83,7 +83,7 @@ init_per_suite(Config) ->
ssh_test_lib:default_algorithms(sshc),
ssh_test_lib:default_algorithms(sshd),
{?DEFAULT_DH_GROUP_MIN,?DEFAULT_DH_GROUP_NBITS,?DEFAULT_DH_GROUP_MAX},
- [KeyLen || {KeyLen,_} <- ?dh_default_groups],
+ public_key:dh_gex_group_sizes(),
?MAX_NUM_ALGORITHMS
]),
ct:log("all() ->~n ~p.~n~ngroups()->~n ~p.~n",[all(),groups()]),
@@ -172,19 +172,50 @@ 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_group14(Config) -> simple_exec_group(2048, Config).
-simple_exec_group15(Config) -> simple_exec_group(3072, Config).
-simple_exec_group16(Config) -> simple_exec_group(4096, Config).
-simple_exec_group17(Config) -> simple_exec_group(6144, Config).
-simple_exec_group18(Config) -> simple_exec_group(8192, Config).
-
-simple_exec_group(I, Config) ->
- Min = I-100,
- Max = I+100,
- {Host,Port} = ?config(srvr_addr, Config),
- ssh_test_lib:std_simple_exec(Host, Port, Config,
- [{dh_gex_limits,{Min,I,Max}}]).
+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
@@ -283,11 +314,10 @@ specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos) ->
case {Tag,Alg} of
{kex,_} when Alg == 'diffie-hellman-group-exchange-sha1' ;
Alg == 'diffie-hellman-group-exchange-sha256' ->
- [simple_exec_group14,
- simple_exec_group15,
- simple_exec_group16,
- simple_exec_group17,
- simple_exec_group18];
+ [simple_exec_groups,
+ simple_exec_groups_no_match_too_large,
+ simple_exec_groups_no_match_too_small
+ ];
_ ->
[]
end.
@@ -331,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_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
index cf15ca4253..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),
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index b84ccac885..3a7f47c2dd 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -66,9 +66,9 @@ groups() ->
{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,
@@ -91,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}]}];
@@ -102,6 +102,16 @@ 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,
@@ -113,10 +123,10 @@ init_per_testcase(_TestCase, Config) ->
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).
@@ -332,28 +342,24 @@ no_common_alg_client_disconnects(Config) ->
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}).
+
+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}}) ->
+do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
{ok,_} =
ssh_trpt_test_lib:exec(
[{set_options, [print_ops, print_seqnums, print_messages]},
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
+