diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/ssh.erl | 165 | ||||
-rw-r--r-- | lib/ssh/src/ssh.hrl | 16 | ||||
-rw-r--r-- | lib/ssh/src/ssh_acceptor.erl | 7 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 171 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.hrl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connect.hrl | 3 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 304 | ||||
-rw-r--r-- | lib/ssh/src/ssh_file.erl | 94 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 153 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftpd.erl | 22 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 925 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.hrl | 35 |
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', + '[email protected]', + '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). |