aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src')
-rw-r--r--lib/ssh/src/ssh.erl165
-rw-r--r--lib/ssh/src/ssh.hrl16
-rw-r--r--lib/ssh/src/ssh_acceptor.erl7
-rw-r--r--lib/ssh/src/ssh_auth.erl171
-rw-r--r--lib/ssh/src/ssh_auth.hrl2
-rw-r--r--lib/ssh/src/ssh_connect.hrl3
-rw-r--r--lib/ssh/src/ssh_connection.erl4
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl304
-rw-r--r--lib/ssh/src/ssh_file.erl94
-rw-r--r--lib/ssh/src/ssh_message.erl153
-rw-r--r--lib/ssh/src/ssh_sftpd.erl22
-rw-r--r--lib/ssh/src/ssh_transport.erl925
-rw-r--r--lib/ssh/src/ssh_transport.hrl35
13 files changed, 1167 insertions, 734 deletions
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index ee44324c12..bb50e436a3 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.
@@ -234,10 +235,27 @@ start_daemon(Host, Port, Options, Inet) ->
{error, _Reason} = Error ->
Error;
{SocketOptions, SshOptions}->
- do_start_daemon(Host, Port,[{role, server} |SshOptions] , [Inet | SocketOptions])
+ try
+ do_start_daemon(Host, Port,[{role, server} |SshOptions] , [Inet | SocketOptions])
+ catch
+ throw:bad_fd -> {error,bad_fd};
+ _C:_E -> {error,{cannot_start_daemon,_C,_E}}
+ end
end.
-do_start_daemon(Host, Port, Options, SocketOptions) ->
+do_start_daemon(Host0, Port0, Options, SocketOptions) ->
+ {Host,Port} = try
+ case proplists:get_value(fd, SocketOptions) of
+ undefined ->
+ {Host0,Port0};
+ Fd when Port0==0 ->
+ find_hostport(Fd);
+ _ ->
+ {Host0,Port0}
+ end
+ catch
+ _:_ -> throw(bad_fd)
+ end,
Profile = proplists:get_value(profile, Options, ?DEFAULT_PROFILE),
case ssh_system_sup:system_supervisor(Host, Port, Profile) of
undefined ->
@@ -271,6 +289,22 @@ do_start_daemon(Host, Port, Options, SocketOptions) ->
end
end.
+find_hostport(Fd) ->
+ %% Using internal functions inet:open/8 and inet:close/0.
+ %% Don't try this at home unless you know what you are doing!
+ {ok,S} = inet:open(Fd, {0,0,0,0}, 0, [], tcp, inet, stream, inet_tcp),
+ {ok, HostPort} = inet:sockname(S),
+ ok = inet:close(S),
+ HostPort.
+
+%% find_port(Fd) ->
+%% %% Hack....
+%% {ok,TmpSock} = gen_tcp:listen(0,[{fd,Fd}]),
+%% {ok, {_,ThePort}} = inet:sockname(TmpSock),
+%% gen_tcp:close(TmpSock),
+%% ThePort.
+
+
handle_options(Opts) ->
try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of
{Inet, Ssh} ->
@@ -337,6 +371,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]);
@@ -391,8 +427,9 @@ 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) ->
@@ -419,27 +456,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;
@@ -461,10 +538,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),
@@ -659,3 +740,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 462c98f503..8efc743b67 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -29,7 +29,6 @@
-define(SSH_DEFAULT_PORT, 22).
-define(SSH_MAX_PACKET_SIZE, (256*1024)).
--define(SSH_LENGHT_INDICATOR_SIZE, 4).
-define(REKEY_TIMOUT, 3600000).
-define(REKEY_DATA_TIMOUT, 60000).
-define(DEFAULT_PROFILE, default).
@@ -37,13 +36,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
@@ -133,9 +135,9 @@
userauth_supported_methods, % string() eg "keyboard-interactive,password"
userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"]
kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive"
- kb_data,
userauth_preference,
available_host_keys,
+ pwdfun_user_state,
authenticated = false
}).
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index c5ad1d7b6c..d94dedf1bf 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -56,7 +56,12 @@ acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) ->
error
end.
-do_socket_listen(Callback, Port, Opts) ->
+do_socket_listen(Callback, Port0, Opts) ->
+ Port =
+ case proplists:get_value(fd, Opts) of
+ undefined -> Port0;
+ _ -> 0
+ end,
case Callback:listen(Port, Opts) of
{error, nxdomain} ->
Callback:listen(Port, lists:delete(inet6, Opts));
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index a91b8c200e..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,
@@ -153,7 +154,7 @@ userauth_request_msg(#ssh{userauth_methods = Methods,
not_ok ->
userauth_request_msg(Ssh);
Result ->
- Result
+ {Pref,Result}
end;
false ->
userauth_request_msg(Ssh)
@@ -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,
@@ -299,8 +300,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
>>
},
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- kb_data = Msg
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User
})}
end;
@@ -313,6 +313,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
#ssh_msg_userauth_failure{authentications = Methods,
partial_success = false}, Ssh)}.
+
+
handle_userauth_info_request(
#ssh_msg_userauth_info_request{name = Name,
instruction = Instr,
@@ -330,36 +332,19 @@ handle_userauth_info_request(
handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
data = <<?UINT32(Sz), Password:Sz/binary>>},
#ssh{opts = Opts,
- kb_tries_left = KbTriesLeft0,
- kb_data = InfoMsg,
+ kb_tries_left = KbTriesLeft,
user = User,
userauth_supported_methods = Methods} = Ssh) ->
- KbTriesLeft = KbTriesLeft0 - 1,
- 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 when KbTriesLeft > 0 ->
- UserAuthInfoMsg =
- InfoMsg#ssh_msg_userauth_info_request{
- name = "",
- instruction =
- lists:concat(
- ["Bad user or password, try again. ",
- integer_to_list(KbTriesLeft),
- " tries left."])
- },
- {not_authorized, {User, undefined},
- ssh_transport:ssh_packet(UserAuthInfoMsg,
- Ssh#ssh{kb_tries_left = KbTriesLeft})};
-
- 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_data = undefined,
- kb_tries_left = 0}
+ Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}
)}
end;
@@ -371,8 +356,6 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{},
language = "en"}).
-default_public_key_algorithms() -> ?PREFERRED_PK_ALGS.
-
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -381,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).
@@ -404,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) ->
@@ -447,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, []).
@@ -471,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);
@@ -501,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_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index 6db89c5d80..9f9f3de8fa 100644
--- a/lib/ssh/src/ssh_connect.hrl
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -248,6 +248,9 @@
local_id, %% local channel id
recv_window_size,
+ recv_window_pending = 0, %% Sum of window size updates that has not
+ %% yet been sent. This limits the number
+ %% of sent update msgs.
recv_packet_size,
recv_close = false,
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 266c64fd4f..a34478732c 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -662,7 +662,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)},
try
- {ok, Pid} = start_subsytem(SsName, Connection, Channel0, ReplyMsg),
+ {ok, Pid} = start_subsystem(SsName, Connection, Channel0, ReplyMsg),
erlang:monitor(process, Pid),
Channel = Channel0#channel{user = Pid},
ssh_channel:cache_update(Cache, Channel),
@@ -1017,7 +1017,7 @@ start_cli(#connection{options = Options,
sub_system_supervisor = SubSysSup}, ChannelId) ->
start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options).
-start_subsytem(BinName, #connection{options = Options,
+start_subsystem(BinName, #connection{options = Options,
sub_system_supervisor = SubSysSup},
#channel{local_id = ChannelId}, _ReplyMsg) ->
Name = binary_to_list(BinName),
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 5b4f6081c1..516a09bf6a 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -49,7 +49,10 @@
-export([hello/2, kexinit/2, key_exchange/2,
key_exchange_dh_gex_init/2, key_exchange_dh_gex_reply/2,
new_keys/2,
- userauth/2, connected/2,
+ service_request/2, connected/2,
+ userauth/2,
+ userauth_keyboard_interactive/2,
+ userauth_keyboard_interactive_info_response/2,
error/2]).
-export([init/1, handle_event/3,
@@ -82,7 +85,12 @@
recbuf
}).
--type state_name() :: hello | kexinit | key_exchange | new_keys | userauth | connection.
+-type state_name() :: hello | kexinit | key_exchange | key_exchange_dh_gex_init |
+ key_exchange_dh_gex_reply | new_keys | service_request |
+ userauth | userauth_keyboard_interactive |
+ userauth_keyboard_interactive_info_response |
+ connection.
+
-type gen_fsm_state_return() :: {next_state, state_name(), term()} |
{next_state, state_name(), term(), timeout()} |
{stop, term(), term()}.
@@ -480,28 +488,30 @@ new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) ->
after_new_keys(next_packet(State0#state{ssh_params = Ssh})).
%%--------------------------------------------------------------------
--spec userauth(#ssh_msg_service_request{} | #ssh_msg_service_accept{} |
- #ssh_msg_userauth_request{} | #ssh_msg_userauth_info_request{} |
- #ssh_msg_userauth_info_response{} | #ssh_msg_userauth_success{} |
- #ssh_msg_userauth_failure{} | #ssh_msg_userauth_banner{},
- #state{}) -> gen_fsm_state_return().
+-spec service_request(#ssh_msg_service_request{} | #ssh_msg_service_accept{},
+ #state{}) -> gen_fsm_state_return().
%%--------------------------------------------------------------------
-
-userauth(#ssh_msg_service_request{name = "ssh-userauth"} = Msg,
+service_request(#ssh_msg_service_request{name = "ssh-userauth"} = Msg,
#state{ssh_params = #ssh{role = server,
session_id = SessionId} = Ssh0} = State) ->
{ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
send_msg(Reply, State),
{next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
-userauth(#ssh_msg_service_accept{name = "ssh-userauth"},
- #state{ssh_params = #ssh{role = client,
- service = "ssh-userauth"} = Ssh0} =
- State) ->
+service_request(#ssh_msg_service_accept{name = "ssh-userauth"},
+ #state{ssh_params = #ssh{role = client,
+ service = "ssh-userauth"} = Ssh0} =
+ State) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
send_msg(Msg, State),
- {next_state, userauth, next_packet(State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh})};
+ {next_state, userauth, next_packet(State#state{auth_user = Ssh#ssh.user, ssh_params = Ssh})}.
+%%--------------------------------------------------------------------
+-spec userauth(#ssh_msg_userauth_request{} | #ssh_msg_userauth_info_request{} |
+ #ssh_msg_userauth_info_response{} | #ssh_msg_userauth_success{} |
+ #ssh_msg_userauth_failure{} | #ssh_msg_userauth_banner{},
+ #state{}) -> gen_fsm_state_return().
+%%--------------------------------------------------------------------
userauth(#ssh_msg_userauth_request{service = "ssh-connection",
method = "none"} = Msg,
#state{ssh_params = #ssh{session_id = SessionId, role = server,
@@ -526,7 +536,11 @@ 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),
+ {next_state, userauth_keyboard_interactive, next_packet(State#state{ssh_params = Ssh})};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
retry_fun(User, Address, Reason, Opts),
send_msg(Reply, State),
@@ -536,30 +550,6 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection",
userauth(Msg#ssh_msg_userauth_request{method="none"}, State)
end;
-userauth(#ssh_msg_userauth_info_request{} = Msg,
- #state{ssh_params = #ssh{role = client,
- io_cb = IoCb} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
-
-userauth(#ssh_msg_userauth_info_response{} = Msg,
- #state{ssh_params = #ssh{role = server,
- peer = {_, Address}} = Ssh0,
- opts = Opts, starter = Pid} = State) ->
- case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of
- {authorized, User, {Reply, Ssh}} ->
- send_msg(Reply, State),
- Pid ! ssh_connected,
- connected_fun(User, Address, "keyboard-interactive", Opts),
- {next_state, connected,
- next_packet(State#state{auth_user = User, ssh_params = Ssh})};
- {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;
-
userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh,
starter = Pid} = State) ->
Pid ! ssh_connected,
@@ -586,19 +576,25 @@ userauth(#ssh_msg_userauth_failure{authentications = Methodes},
{disconnect, DisconnectMsg, {Msg, Ssh}} ->
send_msg(Msg, State),
handle_disconnect(DisconnectMsg, State#state{ssh_params = Ssh});
- {Msg, Ssh} ->
+ {"keyboard-interactive", {Msg, Ssh}} ->
+ send_msg(Msg, State),
+ {next_state, userauth_keyboard_interactive, next_packet(State#state{ssh_params = Ssh})};
+ {_Method, {Msg, Ssh}} ->
send_msg(Msg, State),
{next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
end;
%% The prefered authentication method failed try next method
-userauth(#ssh_msg_userauth_failure{},
+userauth(#ssh_msg_userauth_failure{},
#state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
case ssh_auth:userauth_request_msg(Ssh0) of
{disconnect, DisconnectMsg,{Msg, Ssh}} ->
send_msg(Msg, State),
handle_disconnect(DisconnectMsg, State#state{ssh_params = Ssh});
- {Msg, Ssh} ->
+ {"keyboard-interactive", {Msg, Ssh}} ->
+ send_msg(Msg, State),
+ {next_state, userauth_keyboard_interactive, next_packet(State#state{ssh_params = Ssh})};
+ {_Method, {Msg, Ssh}} ->
send_msg(Msg, State),
{next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
end;
@@ -613,6 +609,50 @@ userauth(#ssh_msg_userauth_banner{message = Msg},
io:format("~s", [Msg]),
{next_state, userauth, next_packet(State)}.
+
+
+userauth_keyboard_interactive(#ssh_msg_userauth_info_request{} = Msg,
+ #state{ssh_params = #ssh{role = client,
+ io_cb = IoCb} = Ssh0} = State) ->
+ {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0),
+ send_msg(Reply, State),
+ {next_state, userauth_keyboard_interactive_info_response, next_packet(State#state{ssh_params = Ssh})};
+
+userauth_keyboard_interactive(#ssh_msg_userauth_info_response{} = Msg,
+ #state{ssh_params = #ssh{role = server,
+ peer = {_, Address}} = Ssh0,
+ opts = Opts, starter = Pid} = State) ->
+ case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of
+ {authorized, User, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ Pid ! ssh_connected,
+ connected_fun(User, Address, "keyboard-interactive", Opts),
+ {next_state, connected,
+ 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;
+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{ssh_params = #ssh{role = client}} = State) ->
+ userauth(Msg, State);
+
+userauth_keyboard_interactive_info_response(Msg=#ssh_msg_userauth_success{},
+ #state{ssh_params = #ssh{role = client}} = State) ->
+ userauth(Msg, State).
+
%%--------------------------------------------------------------------
-spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{},
#state{}) -> gen_fsm_state_return().
@@ -697,13 +737,28 @@ handle_event({adjust_window, ChannelId, Bytes}, StateName,
#connection{channel_cache = Cache}} = State0) ->
State =
case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{recv_window_size = WinSize, remote_id = Id} = Channel ->
- ssh_channel:cache_update(Cache, Channel#channel{recv_window_size =
- WinSize + Bytes}),
- Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes),
+ #channel{recv_window_size = WinSize,
+ recv_window_pending = Pending,
+ recv_packet_size = PktSize} = Channel
+ when (WinSize-Bytes) >= 2*PktSize ->
+ %% The peer can send at least two more *full* packet, no hurry.
+ ssh_channel:cache_update(Cache,
+ Channel#channel{recv_window_pending = Pending + Bytes}),
+ State0;
+
+ #channel{recv_window_size = WinSize,
+ recv_window_pending = Pending,
+ remote_id = Id} = Channel ->
+ %% Now we have to update the window - we can't receive so many more pkts
+ ssh_channel:cache_update(Cache,
+ Channel#channel{recv_window_size =
+ WinSize + Bytes + Pending,
+ recv_window_pending = 0}),
+ Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes + Pending),
send_replies([{connection_reply, Msg}], State0);
- undefined ->
- State0
+
+ undefined ->
+ State0
end,
{next_state, StateName, next_packet(State)};
@@ -936,57 +991,39 @@ handle_info({Protocol, Socket, Info}, hello,
transport_protocol = Protocol} = State) ->
event({info_line, Info}, hello, State);
-handle_info({Protocol, Socket, Data}, Statename,
+handle_info({Protocol, Socket, Data}, StateName,
#state{socket = Socket,
transport_protocol = Protocol,
- ssh_params = #ssh{decrypt_block_size = BlockSize,
- recv_mac_size = MacSize} = Ssh0,
- decoded_data_buffer = <<>>,
- encoded_data_buffer = EncData0} = State0) ->
-
- %% Implementations SHOULD decrypt the length after receiving the
- %% first 8 (or cipher block size, whichever is larger) bytes of a
- %% packet. (RFC 4253: Section 6 - Binary Packet Protocol)
- case size(EncData0) + size(Data) >= erlang:max(8, BlockSize) of
- true ->
- {Ssh, SshPacketLen, DecData, EncData} =
-
- ssh_transport:decrypt_first_block(<<EncData0/binary,
- Data/binary>>, Ssh0),
- case SshPacketLen > ?SSH_MAX_PACKET_SIZE of
- true ->
- DisconnectMsg =
- #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad packet length "
- ++ integer_to_list(SshPacketLen),
- language = "en"},
- handle_disconnect(DisconnectMsg, State0);
- false ->
- RemainingSshPacketLen =
- (SshPacketLen + ?SSH_LENGHT_INDICATOR_SIZE) -
- BlockSize + MacSize,
- State = State0#state{ssh_params = Ssh},
- handle_ssh_packet_data(RemainingSshPacketLen,
- DecData, EncData, Statename,
- State)
- end;
- false ->
- {next_state, Statename,
- next_packet(State0#state{encoded_data_buffer =
- <<EncData0/binary, Data/binary>>})}
+ ssh_params = Ssh0,
+ decoded_data_buffer = DecData0,
+ encoded_data_buffer = EncData0,
+ undecoded_packet_length = RemainingSshPacketLen0} = State0) ->
+ Encoded = <<EncData0/binary, Data/binary>>,
+ case ssh_transport:handle_packet_part(DecData0, Encoded, RemainingSshPacketLen0, Ssh0) of
+ {get_more, DecBytes, EncDataRest, RemainingSshPacketLen, Ssh1} ->
+ {next_state, StateName,
+ next_packet(State0#state{encoded_data_buffer = EncDataRest,
+ decoded_data_buffer = DecBytes,
+ undecoded_packet_length = RemainingSshPacketLen,
+ ssh_params = Ssh1})};
+ {decoded, MsgBytes, EncDataRest, Ssh1} ->
+ generate_event(MsgBytes, StateName,
+ State0#state{ssh_params = Ssh1,
+ %% Important to be set for
+ %% next_packet
+%%% FIXME: the following three seem to always be set in generate_event!
+ decoded_data_buffer = <<>>,
+ undecoded_packet_length = undefined,
+ encoded_data_buffer = EncDataRest},
+ EncDataRest);
+ {bad_mac, Ssh1} ->
+ DisconnectMsg =
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad mac",
+ language = ""},
+ handle_disconnect(DisconnectMsg, State0#state{ssh_params=Ssh1})
end;
-
-handle_info({Protocol, Socket, Data}, Statename,
- #state{socket = Socket,
- transport_protocol = Protocol,
- decoded_data_buffer = DecData,
- encoded_data_buffer = EncData,
- undecoded_packet_length = Len} =
- State) when is_integer(Len) ->
- handle_ssh_packet_data(Len, DecData, <<EncData/binary, Data/binary>>,
- Statename, State);
-
+
handle_info({CloseTag, _Socket}, _StateName,
#state{transport_close_tag = CloseTag,
ssh_params = #ssh{role = _Role, opts = _Opts}} = State) ->
@@ -1064,7 +1101,7 @@ handle_info(UnexpectedMessage, StateName, #state{opts = Opts,
terminate(normal, _, #state{transport_cb = Transport,
connection_state = Connection,
socket = Socket}) ->
- terminate_subsytem(Connection),
+ terminate_subsystem(Connection),
(catch Transport:close(Socket)),
ok;
@@ -1093,7 +1130,7 @@ terminate({shutdown, _}, StateName, State) ->
terminate(Reason, StateName, #state{ssh_params = Ssh0, starter = _Pid,
connection_state = Connection} = State) ->
- terminate_subsytem(Connection),
+ terminate_subsystem(Connection),
log_error(Reason),
DisconnectMsg =
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
@@ -1104,10 +1141,10 @@ terminate(Reason, StateName, #state{ssh_params = Ssh0, starter = _Pid,
terminate(normal, StateName, State#state{ssh_params = Ssh}).
-terminate_subsytem(#connection{system_supervisor = SysSup,
+terminate_subsystem(#connection{system_supervisor = SysSup,
sub_system_supervisor = SubSysSup}) when is_pid(SubSysSup) ->
ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
-terminate_subsytem(_) ->
+terminate_subsystem(_) ->
ok.
format_status(normal, [_, State]) ->
@@ -1242,9 +1279,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
[] ->
@@ -1256,21 +1293,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).
@@ -1569,10 +1602,10 @@ after_new_keys(#state{renegotiate = false,
ssh_params = #ssh{role = client} = Ssh0} = State) ->
{Msg, Ssh} = ssh_auth:service_request_msg(Ssh0),
send_msg(Msg, State),
- {next_state, userauth, State#state{ssh_params = Ssh}};
+ {next_state, service_request, State#state{ssh_params = Ssh}};
after_new_keys(#state{renegotiate = false,
ssh_params = #ssh{role = server}} = State) ->
- {next_state, userauth, State}.
+ {next_state, service_request, State}.
after_new_keys_events({sync, _Event, From}, {stop, _Reason, _StateData}=Terminator) ->
gen_fsm:reply(From, {error, closed}),
@@ -1601,57 +1634,6 @@ after_new_keys_events({connection_reply, _Data} = Reply, {StateName, State}) ->
NewState = send_replies([Reply], State),
{next_state, StateName, NewState}.
-handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName,
- State) ->
- EncSize = size(EncData),
- case RemainingSshPacketLen > EncSize of
- true ->
- {next_state, StateName,
- next_packet(State#state{decoded_data_buffer = DecData,
- encoded_data_buffer = EncData,
- undecoded_packet_length =
- RemainingSshPacketLen})};
- false ->
- handle_ssh_packet(RemainingSshPacketLen, StateName,
- State#state{decoded_data_buffer = DecData,
- encoded_data_buffer = EncData})
-
- end.
-
-handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0,
- encoded_data_buffer = EncData0,
- ssh_params = Ssh0,
- transport_protocol = _Protocol,
- socket = _Socket} = State0) ->
- try
- {Ssh1, DecData, EncData, Mac} =
- ssh_transport:unpack(EncData0, Length, Ssh0),
- SshPacket = <<DecData0/binary, DecData/binary>>,
- case ssh_transport:is_valid_mac(Mac, SshPacket, Ssh1) of
- true ->
- PacketData = ssh_transport:msg_data(SshPacket),
- {Ssh1, Msg} = ssh_transport:decompress(Ssh1, PacketData),
- generate_event(Msg, StateName,
- State0#state{ssh_params = Ssh1,
- %% Important to be set for
- %% next_packet
- decoded_data_buffer = <<>>},
- EncData);
- false ->
- DisconnectMsg =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad mac",
- language = "en"},
- handle_disconnect(DisconnectMsg, State0)
- end
- catch _:_ ->
- Disconnect =
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad input",
- language = "en"},
- handle_disconnect(Disconnect, State0)
- end.
-
handle_disconnect(DisconnectMsg, State) ->
handle_disconnect(own, DisconnectMsg, State).
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index b98a8a8410..3e066c453d 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};
@@ -210,29 +221,32 @@ 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";
-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 ->
{error, not_found};
+ {error,Error} ->
+ %% Rare... For example NFS errors
+ {error,Error};
Line ->
case ssh_decode_line(Line, known_hosts) of
[{Key, Attributes}] ->
@@ -253,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.
@@ -267,6 +281,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.
@@ -293,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, _}] ->
@@ -312,8 +336,18 @@ is_auth_key(Key, Key) ->
is_auth_key(_,_) ->
false.
-default_user_dir()->
- {ok,[[Home|_]]} = init:get_argument(home),
+
+default_user_dir() ->
+ try
+ default_user_dir(os:getenv("HOME"))
+ catch
+ _:_ ->
+ default_user_dir(init:get_argument(home))
+ end.
+
+default_user_dir({ok,[[Home|_]]}) ->
+ default_user_dir(Home);
+default_user_dir(Home) when is_list(Home) ->
UserDir = filename:join(Home, ".ssh"),
ok = filelib:ensure_dir(filename:join(UserDir, "dummy")),
{ok,Info} = file:read_file_info(UserDir),
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_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index a6549f1c73..819cba697e 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -30,6 +30,7 @@
-include("ssh.hrl").
-include("ssh_xfer.hrl").
+-include("ssh_connect.hrl"). %% For ?DEFAULT_PACKET_SIZE and ?DEFAULT_WINDOW_SIZE
%%--------------------------------------------------------------------
%% External exports
@@ -47,6 +48,7 @@
file_handler, % atom() - callback module
file_state, % state for the file callback module
max_files, % integer >= 0 max no files sent during READDIR
+ options, % from the subsystem declaration
handles % list of open handles
%% handle is either {<int>, directory, {Path, unread|eof}} or
%% {<int>, file, {Path, IoDevice}}
@@ -121,6 +123,7 @@ init(Options) ->
MaxLength = proplists:get_value(max_files, Options, 0),
Vsn = proplists:get_value(sftpd_vsn, Options, 5),
{ok, State#state{cwd = CWD, root = Root, max_files = MaxLength,
+ options = Options,
handles = [], pending = <<>>,
xf = #ssh_xfer{vsn = Vsn, ext = []}}}.
@@ -164,7 +167,9 @@ handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
%% Description: Handles other messages
%%--------------------------------------------------------------------
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
- #state{xf =Xf} = State) ->
+ #state{xf = Xf,
+ options = Options} = State) ->
+ maybe_increase_recv_window(ConnectionManager, ChannelId, Options),
{ok, State#state{xf = Xf#ssh_xfer{cm = ConnectionManager,
channel = ChannelId}}}.
@@ -934,3 +939,18 @@ rename(Path, Path2, ReqId, State0) ->
{Status, FS1} = FileMod:rename(Path, Path2, FS0),
State1 = State0#state{file_state = FS1},
send_status(Status, ReqId, State1).
+
+
+maybe_increase_recv_window(ConnectionManager, ChannelId, Options) ->
+ WantedRecvWindowSize =
+ proplists:get_value(recv_window_size, Options, 1000000),
+ NumPkts = WantedRecvWindowSize div ?DEFAULT_PACKET_SIZE,
+ Increment = NumPkts*?DEFAULT_PACKET_SIZE - ?DEFAULT_WINDOW_SIZE,
+
+ if
+ Increment > 0 ->
+ ssh_connection:adjust_window(ConnectionManager, ChannelId,
+ Increment);
+ Increment =< 0 ->
+ do_nothing
+ end.
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index e3ee399b8e..67a0d29bb8 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -31,10 +31,10 @@
-include("ssh.hrl").
-export([versions/2, hello_version_msg/1]).
--export([next_seqnum/1, decrypt_first_block/2, decrypt_blocks/3,
+-export([next_seqnum/1,
supported_algorithms/0, supported_algorithms/1,
default_algorithms/0, default_algorithms/1,
- is_valid_mac/3,
+ handle_packet_part/4,
handle_hello_version/1,
key_exchange_init_msg/1,
key_init/3, new_keys_message/1,
@@ -44,9 +44,14 @@
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,
+ ssh_packet/2, pack/2,
sign/3, verify/4]).
+%%% For test suites
+-export([pack/3]).
+-export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove
+
%%%----------------------------------------------------------------------------
%%%
%%% There is a difference between supported and default algorithms. The
@@ -65,11 +70,15 @@ 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(cipher) ->
+ supported_algorithms(cipher, same(['AEAD_AES_128_GCM',
+ 'AEAD_AES_256_GCM']));
+default_algorithms(mac) ->
+ supported_algorithms(mac, same(['AEAD_AES_128_GCM',
+ 'AEAD_AES_256_GCM']));
default_algorithms(Alg) ->
- supported_algorithms(Alg).
+ supported_algorithms(Alg, []).
supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()].
@@ -79,58 +88,50 @@ 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}]},
- {'aes128-cbc', [{ciphers,aes_cbc128}]},
- {'3des-cbc', [{ciphers,des3_cbc}]}
+ [{'aes256-ctr', [{ciphers,{aes_ctr,256}}]},
+ {'aes192-ctr', [{ciphers,{aes_ctr,192}}]},
+ {'aes128-ctr', [{ciphers,{aes_ctr,128}}]},
+ {'aes128-cbc', [{ciphers,aes_cbc128}]},
+ {'[email protected]', [{ciphers,{aes_gcm,128}}]},
+ {'[email protected]', [{ciphers,{aes_gcm,256}}]},
+ {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+ {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]},
+ {'3des-cbc', [{ciphers,des3_cbc}]}
]
));
supported_algorithms(mac) ->
same(
select_crypto_supported(
- [{'hmac-sha2-512', [{hashs,sha512}]},
- {'hmac-sha2-256', [{hashs,sha256}]},
- {'hmac-sha1', [{hashs,sha}]}
+ [{'hmac-sha2-256', [{hashs,sha256}]},
+ {'hmac-sha2-512', [{hashs,sha512}]},
+ {'hmac-sha1', [{hashs,sha}]},
+ {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+ {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]}
]
));
supported_algorithms(compression) ->
- same(['none','zlib','[email protected]']).
-
-
-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.
-
-select_crypto_supported(L) ->
- Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()],
- [Name || {Name,CryptoRequires} <- L,
- crypto_supported(CryptoRequires, Sup)].
-
-crypto_supported_curves() ->
- try crypto:ec_curves()
- catch _:_ -> []
- end.
-
-crypto_supported(Conditions, Supported) ->
- lists:all( fun({Tag,CryptoName}) ->
- lists:member(CryptoName, proplists:get_value(Tag,Supported,[]))
- end, Conditions).
-
-
-same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
-
+ same(['none',
+ 'zlib'
+ ]).
%%%----------------------------------------------------------------------------
versions(client, Options)->
@@ -170,12 +171,6 @@ hello_version_msg(Data) ->
next_seqnum(SeqNum) ->
(SeqNum + 1) band 16#ffffffff.
-decrypt_first_block(Bin, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
- <<EncBlock:BlockSize/binary, EncData/binary>> = Bin,
- {Ssh, <<?UINT32(PacketLen), _/binary>> = DecData} =
- decrypt(Ssh0, EncBlock),
- {Ssh, PacketLen, DecData, EncData}.
-
decrypt_blocks(Bin, Length, Ssh0) ->
<<EncBlocks:Length/binary, EncData/binary>> = Bin,
{Ssh, DecData} = decrypt(Ssh0, EncBlocks),
@@ -303,9 +298,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 +312,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 +348,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 +371,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 +379,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 +410,28 @@ 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(#ssh_msg_kex_dh_gex_request_old{n = NBits},
Ssh0=#ssh{opts=Opts}) ->
@@ -442,16 +447,25 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits},
%% Further, it says that "Servers and clients SHOULD support groups
%% with a modulus length of k bits, where 1024 <= k <= 8192."
%%
- Min = NBits,
- Max = 8192,
- {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 = {-1, -1, NBits} % flag for kex_h hash calc
- }};
+ Min0 = NBits,
+ Max0 = 8192,
+ {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 = {-1, -1, NBits} % flag for kex_h hash calc
+ }};
+ {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},
@@ -461,6 +475,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]),
@@ -480,11 +514,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,
@@ -508,7 +543,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}},
@@ -520,9 +555,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,
@@ -561,24 +596,26 @@ 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),
- HostKey = get_host_key(Ssh0),
- H = kex_h(Ssh0, Curve, HostKey, PeerPublic, MyPublic, K),
- H_SIG = sign_host_key(Ssh0, HostKey, H),
+ {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),
+ 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),
{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,
@@ -587,17 +624,18 @@ 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
) ->
%% at client
- 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
+ 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 ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
{ok, SshPacket, Ssh#ssh{shared_secret = K,
@@ -610,9 +648,9 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = HostKey,
description = "Key exchange failed",
language = ""}
})
- end;
-
- false ->
+ end
+ catch
+ _:_ ->
throw({{error,invalid_peer_public_key},
#ssh_msg_disconnect{
code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
@@ -622,8 +660,6 @@ 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 :)
-
%%%----------------------------------------------------------------
handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
try install_alg(Ssh0) of
@@ -649,33 +685,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
@@ -708,8 +760,12 @@ known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh,
%% The first algorithm in each list MUST be the preferred (guessed)
%% algorithm. Each string MUST contain at least one algorithm name.
select_algorithm(Role, Client, Server) ->
- {Encrypt, Decrypt} = select_encrypt_decrypt(Role, Client, Server),
- {SendMac, RecvMac} = select_send_recv_mac(Role, Client, Server),
+ {Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
+ {SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
+
+ {Encrypt, SendMac} = aead_gcm_simultan(Encrypt0, SendMac0),
+ {Decrypt, RecvMac} = aead_gcm_simultan(Decrypt0, RecvMac0),
+
{Compression, Decompression} =
select_compression_decompression(Role, Client, Server),
@@ -740,6 +796,38 @@ select_algorithm(Role, Client, Server) ->
s_lng = S_Lng},
{ok, Alg}.
+
+%%% It is an agreed problem with RFC 5674 that if the selection is
+%%% Cipher = AEAD_AES_x_GCM and
+%%% Mac = AEAD_AES_y_GCM (where x =/= y)
+%%% then it is undefined what length should be selected.
+%%%
+%%% If only one of the two lengths (128,256) is available, I claim that
+%%% there is no such ambiguity.
+
+%%% From https://anongit.mindrot.org/openssh.git/plain/PROTOCOL
+%%% (read Nov 20, 2015)
+%%% 1.6 transport: AES-GCM
+%%%
+%%% OpenSSH supports the AES-GCM algorithm as specified in RFC 5647.
+%%% Because of problems with the specification of the key exchange
+%%% the behaviour of OpenSSH differs from the RFC as follows:
+%%%
+%%% AES-GCM is only negotiated as the cipher algorithms
+%%% "[email protected]" or "[email protected]" and never as
+%%% an MAC algorithm. Additionally, if AES-GCM is selected as the cipher
+%%% the exchanged MAC algorithms are ignored and there doesn't have to be
+%%% a matching MAC.
+
+aead_gcm_simultan('[email protected]', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'};
+aead_gcm_simultan('[email protected]', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'};
+aead_gcm_simultan('AEAD_AES_128_GCM', _) -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'};
+aead_gcm_simultan('AEAD_AES_256_GCM', _) -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'};
+aead_gcm_simultan(_, 'AEAD_AES_128_GCM') -> {'AEAD_AES_128_GCM', 'AEAD_AES_128_GCM'};
+aead_gcm_simultan(_, 'AEAD_AES_256_GCM') -> {'AEAD_AES_256_GCM', 'AEAD_AES_256_GCM'};
+aead_gcm_simultan(Cipher, Mac) -> {Cipher,Mac}.
+
+
select_encrypt_decrypt(client, Client, Server) ->
Encrypt =
select(Client#ssh_msg_kexinit.encryption_algorithms_client_to_server,
@@ -774,18 +862,18 @@ select_compression_decompression(client, Client, Server) ->
Compression =
select(Client#ssh_msg_kexinit.compression_algorithms_client_to_server,
Server#ssh_msg_kexinit.compression_algorithms_client_to_server),
- Decomprssion =
+ Decompression =
select(Client#ssh_msg_kexinit.compression_algorithms_server_to_client,
Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
- {Compression, Decomprssion};
+ {Compression, Decompression};
select_compression_decompression(server, Client, Server) ->
- Decomprssion =
+ Decompression =
select(Client#ssh_msg_kexinit.compression_algorithms_client_to_server,
Server#ssh_msg_kexinit.compression_algorithms_client_to_server),
Compression =
select(Client#ssh_msg_kexinit.compression_algorithms_server_to_client,
Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
- {Compression, Decomprssion}.
+ {Compression, Decompression}.
install_alg(SSH) ->
SSH1 = alg_final(SSH),
@@ -856,13 +944,45 @@ ssh_packet(Msg, Ssh) ->
BinMsg = ssh_message:encode(Msg),
pack(BinMsg, Ssh).
-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) ->
- {Ssh1, Data} = compress(Ssh0, Data0),
- PL = (BlockSize - ((4 + 1 + size(Data)) rem BlockSize)) rem BlockSize,
+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(PlainText,
+ #ssh{send_sequence = SeqNum,
+ send_mac = MacAlg,
+ send_mac_key = MacKey,
+ encrypt = CryptoAlg} = Ssh0, PacketLenDeviationForTests) when is_binary(PlainText) ->
+
+ {Ssh1, CompressedPlainText} = compress(Ssh0, PlainText),
+ {EcryptedPacket, MAC, Ssh3} =
+ case pkt_type(CryptoAlg) of
+ common ->
+ PaddingLen = padding_length(4+1+size(CompressedPlainText), Ssh0),
+ Padding = ssh_bits:random(PaddingLen),
+ PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests,
+ PlainPacketData = <<?UINT32(PlainPacketLen),?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>,
+ {Ssh2, EcryptedPacket0} = encrypt(Ssh1, PlainPacketData),
+ MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPacketData),
+ {EcryptedPacket0, MAC0, Ssh2};
+ aead ->
+ PaddingLen = padding_length(1+size(CompressedPlainText), Ssh0),
+ Padding = ssh_bits:random(PaddingLen),
+ PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests,
+ PlainPacketData = <<?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>,
+ {Ssh2, {EcryptedPacket0,MAC0}} = encrypt(Ssh1, {<<?UINT32(PlainPacketLen)>>,PlainPacketData}),
+ {<<?UINT32(PlainPacketLen),EcryptedPacket0/binary>>, MAC0, Ssh2}
+ end,
+ FinalPacket = [EcryptedPacket, MAC],
+ Ssh = Ssh3#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
+ {FinalPacket, Ssh}.
+
+
+padding_length(Size, #ssh{encrypt_block_size = BlockSize,
+ random_length_padding = RandomLengthPadding}) ->
+ PL = (BlockSize - (Size rem BlockSize)) rem BlockSize,
MinPaddingLen = if PL < 4 -> PL + BlockSize;
true -> PL
end,
@@ -871,50 +991,103 @@ pack(Data0, #ssh{encrypt_block_size = BlockSize,
ExtraPaddingLen = try crypto:rand_uniform(0,MaxExtraBlocks)*PadBlockSize
catch _:_ -> 0
end,
- PaddingLen = MinPaddingLen + ExtraPaddingLen,
- Padding = ssh_bits:random(PaddingLen),
- PacketLen = 1 + PaddingLen + size(Data),
- PacketData = <<?UINT32(PacketLen),?BYTE(PaddingLen),
- Data/binary, Padding/binary>>,
- {Ssh2, EncPacket} = encrypt(Ssh1, PacketData),
- MAC = mac(MacAlg, MacKey, SeqNum, PacketData),
- Packet = [EncPacket, MAC],
- Ssh = Ssh2#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
- {Packet, Ssh}.
-
-unpack(EncodedSoFar, ReminingLenght, #ssh{recv_mac_size = MacSize} = Ssh0) ->
- SshLength = ReminingLenght - MacSize,
- {NoMac, Mac, Rest} = case MacSize of
- 0 ->
- <<NoMac0:SshLength/binary,
- Rest0/binary>> = EncodedSoFar,
- {NoMac0, <<>>, Rest0};
- _ ->
- <<NoMac0:SshLength/binary,
- Mac0:MacSize/binary,
- Rest0/binary>> = EncodedSoFar,
- {NoMac0, Mac0, Rest0}
- end,
- {Ssh1, DecData, <<>>} =
- case SshLength of
- 0 ->
- {Ssh0, <<>>, <<>>};
- _ ->
- decrypt_blocks(NoMac, SshLength, Ssh0)
- end,
- {Ssh1, DecData, Rest, Mac}.
+ MinPaddingLen + ExtraPaddingLen.
+
+
+
+handle_packet_part(<<>>, Encrypted0, undefined, #ssh{decrypt = CryptoAlg} = Ssh0) ->
+ %% New ssh packet
+ case get_length(pkt_type(CryptoAlg), Encrypted0, Ssh0) of
+ get_more ->
+ %% too short to get the length
+ {get_more, <<>>, Encrypted0, undefined, Ssh0};
+
+ {ok, PacketLen, _, _, _} when PacketLen > ?SSH_MAX_PACKET_SIZE ->
+ %% far too long message than expected
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad packet length "
+ ++ integer_to_list(PacketLen),
+ language = ""});
+
+ {ok, PacketLen, Decrypted, Encrypted1,
+ #ssh{recv_mac_size = MacSize} = Ssh1} ->
+ %% enough bytes so we got the length and can calculate how many
+ %% more bytes to expect for a full packet
+ TotalNeeded = (4 + PacketLen + MacSize),
+ handle_packet_part(Decrypted, Encrypted1, TotalNeeded, Ssh1)
+ end;
+
+handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0)
+ when (size(DecryptedPfx)+size(EncryptedBuffer)) < TotalNeeded ->
+ %% need more bytes to finalize the packet
+ {get_more, DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0};
+
+handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded,
+ #ssh{recv_mac_size = MacSize,
+ decrypt = CryptoAlg} = Ssh0) ->
+ %% enough bytes to decode the packet.
+ DecryptLen = TotalNeeded - size(DecryptedPfx) - MacSize,
+ <<EncryptedSfx:DecryptLen/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
+ case pkt_type(CryptoAlg) of
+ common ->
+ {Ssh1, DecryptedSfx} = decrypt(Ssh0, EncryptedSfx),
+ DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
+ case is_valid_mac(Mac, DecryptedPacket, Ssh1) of
+ false ->
+ {bad_mac, Ssh1};
+ true ->
+ {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)),
+ {decoded, DecompressedPayload, NextPacketBytes, Ssh}
+ end;
+ aead ->
+ PacketLenBin = DecryptedPfx,
+ case decrypt(Ssh0, {PacketLenBin,EncryptedSfx,Mac}) of
+ {Ssh1, error} ->
+ {bad_mac, Ssh1};
+ {Ssh1, DecryptedSfx} ->
+ DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
+ {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)),
+ {decoded, DecompressedPayload, NextPacketBytes, Ssh}
+ end
+ end.
+
+
+get_length(common, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
+ case size(EncryptedBuffer) >= erlang:max(8, BlockSize) of
+ true ->
+ <<EncBlock:BlockSize/binary, EncryptedRest/binary>> = EncryptedBuffer,
+ {Ssh,
+ <<?UINT32(PacketLen),_/binary>> = Decrypted} = decrypt(Ssh0, EncBlock),
+ {ok, PacketLen, Decrypted, EncryptedRest, Ssh};
+ false ->
+ get_more
+ end;
+get_length(aead, EncryptedBuffer, Ssh) ->
+ case size(EncryptedBuffer) >= 4 of
+ true ->
+ <<?UINT32(PacketLen), EncryptedRest/binary>> = EncryptedBuffer,
+ {ok, PacketLen, <<?UINT32(PacketLen)>>, EncryptedRest, Ssh};
+ false ->
+ get_more
+ end.
+
+pkt_type('AEAD_AES_128_GCM') -> aead;
+pkt_type('AEAD_AES_256_GCM') -> aead;
+pkt_type(_) -> common.
-msg_data(PacketData) ->
- <<Len:32, PaddingLen:8, _/binary>> = PacketData,
- DataLen = Len - PaddingLen - 1,
- <<_:32, _:8, Data:DataLen/binary,
- _:PaddingLen/binary>> = PacketData,
- Data.
+payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
+ PayloadLen = PacketLen - PaddingLen - 1,
+ <<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
+ Payload.
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).
@@ -922,60 +1095,48 @@ 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
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
encrypt_init(#ssh{encrypt = none} = Ssh) ->
{ok, Ssh};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:16/binary>> = hash(Ssh, "C", 128),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:16/binary>> = hash(Ssh, "D", 128),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:32/binary>> = hash(Ssh, "C", 256),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:32/binary>> = hash(Ssh, "D", 256),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
encrypt_init(#ssh{encrypt = '3des-cbc', role = client} = Ssh) ->
IV = hash(Ssh, "A", 64),
<<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "C", 192),
@@ -1001,18 +1162,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) ->
@@ -1024,6 +1213,18 @@ encrypt_final(Ssh) ->
encrypt(#ssh{encrypt = none} = Ssh, Data) ->
{Ssh, Data};
+encrypt(#ssh{encrypt = 'AEAD_AES_128_GCM',
+ encrypt_keys = K,
+ encrypt_ctx = IV0} = Ssh, Data={_AAD,_Ptext}) ->
+ Enc = {_Ctext,_Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, Data),
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{encrypt_ctx = IV}, Enc};
+encrypt(#ssh{encrypt = 'AEAD_AES_256_GCM',
+ encrypt_keys = K,
+ encrypt_ctx = IV0} = Ssh, Data={_AAD,_Ptext}) ->
+ Enc = {_Ctext,_Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, Data),
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{encrypt_ctx = IV}, Enc};
encrypt(#ssh{encrypt = '3des-cbc',
encrypt_keys = {K1,K2,K3},
encrypt_ctx = IV0} = Ssh, Data) ->
@@ -1039,6 +1240,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}.
@@ -1048,6 +1257,30 @@ encrypt(#ssh{encrypt = 'aes128-ctr',
decrypt_init(#ssh{decrypt = none} = Ssh) ->
{ok, Ssh};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:16/binary>> = hash(Ssh, "D", 128),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:16/binary>> = hash(Ssh, "C", 128),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) ->
+ IV = hash(Ssh, "B", 12*8),
+ <<K:32/binary>> = hash(Ssh, "D", 256),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
+decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) ->
+ IV = hash(Ssh, "A", 12*8),
+ <<K:32/binary>> = hash(Ssh, "C", 256),
+ {ok, Ssh#ssh{decrypt_keys = K,
+ decrypt_block_size = 16,
+ decrypt_ctx = IV}};
decrypt_init(#ssh{decrypt = '3des-cbc', role = client} = Ssh) ->
{IV, KD} = {hash(Ssh, "B", 64),
hash(Ssh, "D", 192)},
@@ -1079,12 +1312,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}}.
@@ -1094,8 +1355,22 @@ decrypt_final(Ssh) ->
decrypt_ctx = undefined,
decrypt_block_size = 8}}.
+decrypt(Ssh, <<>>) ->
+ {Ssh, <<>>};
decrypt(#ssh{decrypt = none} = Ssh, Data) ->
{Ssh, Data};
+decrypt(#ssh{decrypt = 'AEAD_AES_128_GCM',
+ decrypt_keys = K,
+ decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) ->
+ Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{decrypt_ctx = IV}, Dec};
+decrypt(#ssh{decrypt = 'AEAD_AES_256_GCM',
+ decrypt_keys = K,
+ decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) ->
+ Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error
+ IV = next_gcm_iv(IV0),
+ {Ssh#ssh{decrypt_ctx = IV}, Dec};
decrypt(#ssh{decrypt = '3des-cbc', decrypt_keys = Keys,
decrypt_ctx = IV0} = Ssh, Data) ->
{K1, K2, K3} = Keys,
@@ -1110,8 +1385,20 @@ 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}.
+
+next_gcm_iv(<<Fixed:32, InvCtr:64>>) -> <<Fixed:32, (InvCtr+1):64>>.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compression
%%
@@ -1194,43 +1481,48 @@ 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
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
send_mac_init(SSH) ->
- case SSH#ssh.role of
- client ->
- KeySize =mac_key_size(SSH#ssh.send_mac),
- Key = hash(SSH, "E", KeySize),
- {ok, SSH#ssh { send_mac_key = Key }};
- server ->
- KeySize = mac_key_size(SSH#ssh.send_mac),
- Key = hash(SSH, "F", KeySize),
- {ok, SSH#ssh { send_mac_key = Key }}
+ case pkt_type(SSH#ssh.send_mac) of
+ common ->
+ case SSH#ssh.role of
+ client ->
+ KeySize = mac_key_size(SSH#ssh.send_mac),
+ Key = hash(SSH, "E", KeySize),
+ {ok, SSH#ssh { send_mac_key = Key }};
+ server ->
+ KeySize = mac_key_size(SSH#ssh.send_mac),
+ Key = hash(SSH, "F", KeySize),
+ {ok, SSH#ssh { send_mac_key = Key }}
+ end;
+ aead ->
+ %% Not applicable
+ {ok, SSH}
end.
send_mac_final(SSH) ->
- {ok, SSH#ssh { send_mac = none, send_mac_key = undefined }}.
+ {ok, SSH#ssh {send_mac = none,
+ send_mac_key = undefined }}.
+
recv_mac_init(SSH) ->
- case SSH#ssh.role of
- client ->
- Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
- {ok, SSH#ssh { recv_mac_key = Key }};
- server ->
- Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
- {ok, SSH#ssh { recv_mac_key = Key }}
+ case pkt_type(SSH#ssh.recv_mac) of
+ common ->
+ case SSH#ssh.role of
+ client ->
+ Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
+ {ok, SSH#ssh { recv_mac_key = Key }};
+ server ->
+ Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
+ {ok, SSH#ssh { recv_mac_key = Key }}
+ end;
+ aead ->
+ %% Not applicable
+ {ok, SSH}
end.
recv_mac_final(SSH) ->
@@ -1294,23 +1586,26 @@ 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) ->
+ KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
L = if Min==-1; Max==-1 ->
%% flag from 'ssh_msg_kex_dh_gex_request_old'
%% It was like this before that message was supported,
@@ -1320,7 +1615,7 @@ kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) ->
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 ->
Ts = [string,string,binary,binary,binary,
@@ -1328,21 +1623,23 @@ kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) ->
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;
@@ -1358,6 +1655,8 @@ mac_digest_size('hmac-md5') -> 20;
mac_digest_size('hmac-md5-96') -> 12;
mac_digest_size('hmac-sha2-256') -> 32;
mac_digest_size('hmac-sha2-512') -> 64;
+mac_digest_size('AEAD_AES_128_GCM') -> 16;
+mac_digest_size('AEAD_AES_256_GCM') -> 16;
mac_digest_size(none) -> 0.
peer_name({Host, _}) ->
@@ -1369,32 +1668,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) ->
- %% Try to find an exact match. If not an exact match, select the first found.
- case lists:keyfind(N, 1, Groups) of
- {N,Grp} ->
- Grp;
- false ->
- case lists:dropwhile(fun({I,_}) -> I < Min-1 orelse I > Max+1 end,
- Groups) of
- [{_,Grp}|_] ->
- Grp;
- [] ->
- throw(#ssh_msg_disconnect{
- code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "No possible diffie-hellman-group-exchange group found",
- language = ""})
- end
- 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)}.
@@ -1409,6 +1686,68 @@ ecdh_curve('ecdh-sha2-nistp256') -> secp256r1;
ecdh_curve('ecdh-sha2-nistp384') -> secp384r1;
ecdh_curve('ecdh-sha2-nistp521') -> secp521r1.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Utils for default_algorithms/1 and supported_algorithms/1
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+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.
+
+
+select_crypto_supported(L) ->
+ Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()],
+ [Name || {Name,CryptoRequires} <- L,
+ crypto_supported(CryptoRequires, Sup)].
+
+crypto_supported_curves() ->
+ try crypto:ec_curves()
+ catch _:_ -> []
+ end.
+
+crypto_supported(Conditions, Supported) ->
+ lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) ->
+ crypto_name_supported(Tag,CryptoName,Supported);
+ ({Tag,{Name,Len}}) when is_integer(Len) ->
+ crypto_name_supported(Tag,Name,Supported) andalso
+ len_supported(Name,Len)
+ end, Conditions).
+
+crypto_name_supported(Tag, CryptoName, Supported) ->
+ lists:member(CryptoName, proplists:get_value(Tag,Supported,[])).
+
+len_supported(Name, Len) ->
+ try
+ case Name of
+ aes_ctr ->
+ {_, <<_/binary>>} =
+ %% Test encryption
+ crypto:stream_encrypt(crypto:stream_init(Name, <<0:Len>>, <<0:128>>), <<"">>);
+ aes_gcm ->
+ {<<_/binary>>, <<_/binary>>} =
+ crypto:block_encrypt(Name,
+ _Key = <<0:Len>>,
+ _IV = <<0:12/unsigned-unit:8>>,
+ {<<"AAD">>,"PT"})
+ end
+ of
+ _ -> true
+ catch
+ _:_ -> false
+ end.
+
+
+same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
+
+
+%% default_algorithms(kex) -> % Example of how to disable an algorithm
+%% supported_algorithms(kex, ['ecdh-sha2-nistp521']);
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Other utils
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).