diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/Makefile | 1 | ||||
-rw-r--r-- | lib/ssh/src/ssh.app.src | 1 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 26 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 42 | ||||
-rw-r--r-- | lib/ssh/src/ssh_math.erl | 42 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 17 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftp.erl | 5 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 409 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.hrl | 61 |
9 files changed, 387 insertions, 217 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 61d71d2cf7..98fb90d7c4 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -67,7 +67,6 @@ MODULES= \ ssh_file \ ssh_io \ ssh_info \ - ssh_math \ ssh_message \ ssh_no_io \ ssh_sftp \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index bc01c539e0..4a76fd9cd3 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -24,7 +24,6 @@ ssh_file, ssh_io, ssh_info, - ssh_math, ssh_no_io, ssh_server_key_api, ssh_sftp, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 370f086600..5b2e0a988c 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -373,6 +373,10 @@ handle_option([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{preferred_algorithms,_} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{dh_gex_groups,_} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{dh_gex_limits,_} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> @@ -411,6 +415,28 @@ 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_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0, + is_integer(I), I>=Min, + is_integer(Max), Max>=I -> + Opt; handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> Opt; handle_ssh_option({max_sessions, Value} = Opt) when is_integer(Value), Value>0 -> diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index a9c60d0674..c059834b27 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -46,7 +46,9 @@ get_print_info/1]). %% gen_fsm callbacks --export([hello/2, kexinit/2, key_exchange/2, new_keys/2, +-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, error/2]). @@ -417,27 +419,39 @@ key_exchange(#ssh_msg_kexdh_reply{} = Msg, send_msg(NewKeys, State), {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}; -key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg, +key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg, #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, NextKexMsg, Ssh1} = ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0), - send_msg(NextKexMsg, State), - {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), - send_msg(NewKeys, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}; + {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), + send_msg(GexGroup, State), + {next_state, key_exchange_dh_gex_init, next_packet(State#state{ssh_params = Ssh})}; -key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg, +key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg, #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - {ok, NextKexMsg, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0), - send_msg(NextKexMsg, State), - {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}; + {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0), + send_msg(KexGexInit, State), + {next_state, key_exchange_dh_gex_reply, next_packet(State#state{ssh_params = Ssh})}. -key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg, - #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> - {ok, NewKeys, Ssh} = ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0), +%%-------------------------------------------------------------------- +-spec key_exchange_dh_gex_init(#ssh_msg_kex_dh_gex_init{}, #state{}) -> gen_fsm_state_return(). +%%-------------------------------------------------------------------- +key_exchange_dh_gex_init(#ssh_msg_kex_dh_gex_init{} = Msg, + #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> + {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, Ssh0), + send_msg(KexGexReply, State), + {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1), send_msg(NewKeys, State), {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}. %%-------------------------------------------------------------------- +-spec key_exchange_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{}, #state{}) -> gen_fsm_state_return(). +%%-------------------------------------------------------------------- +key_exchange_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{} = Msg, + #state{ssh_params = #ssh{role = client} = Ssh0} = State) -> + {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0), + send_msg(NewKeys, State), + {next_state, new_keys, next_packet(State#state{ssh_params = Ssh1})}. + +%%-------------------------------------------------------------------- -spec new_keys(#ssh_msg_newkeys{}, #state{}) -> gen_fsm_state_return(). %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_math.erl b/lib/ssh/src/ssh_math.erl deleted file mode 100644 index cace85bc93..0000000000 --- a/lib/ssh/src/ssh_math.erl +++ /dev/null @@ -1,42 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2013. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% - -%%% Description: SSH math utilities - --module(ssh_math). - --export([ipow/3]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -%% INTEGER utils -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% calculate A^B mod M -ipow(A, B, M) when M > 0, B >= 0 -> - crypto:bytes_to_integer(crypto:mod_pow(A, B, M)). - - - - - diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 1f0f6fb15f..7b786b8fff 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -237,7 +237,7 @@ encode(#ssh_msg_kex_dh_gex_request{ max = Max }) -> ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REQUEST, Min, N, Max], - [byte, uint32, uint32, uint32, uint32]); + [byte, uint32, uint32, uint32]); encode(#ssh_msg_kex_dh_gex_request_old{n = N}) -> ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REQUEST_OLD, N], [byte, uint32]); @@ -257,7 +257,7 @@ encode(#ssh_msg_kex_dh_gex_reply{ }) -> EncKey = encode_host_key(Key), EncSign = encode_sign(Key, Signature), - ssh_bits:encode([?SSH_MSG_KEXDH_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]); + ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]); encode(#ssh_msg_ignore{data = Data}) -> ssh_bits:encode([?SSH_MSG_IGNORE, Data], [byte, string]); @@ -442,6 +442,19 @@ decode(<<?BYTE(?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>>) -> + #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>>) -> + #ssh_msg_kex_dh_gex_reply{ + public_host_key = decode_host_key(Key), + f = F, + h_sig = decode_sign(Hashsign) + }; decode(<<?BYTE(?SSH_MSG_KEXDH_REPLY), ?UINT32(Len0), Key:Len0/binary, ?UINT32(Len1), F:Len1/big-signed-integer-unit:8, ?UINT32(Len2), Hashsign:Len2/binary>>) -> diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 9fe2d56759..dbacf730cc 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -439,7 +439,7 @@ write_file(Pid, Name, List) -> write_file(Pid, Name, List, ?FILEOP_TIMEOUT). write_file(Pid, Name, List, FileOpTimeout) when is_list(List) -> - write_file(Pid, Name, unicode:characters_to_binary(List), FileOpTimeout); + write_file(Pid, Name, list_to_binary(List), FileOpTimeout); write_file(Pid, Name, Bin, FileOpTimeout) -> case open(Pid, Name, [write, binary], FileOpTimeout) of {ok, Handle} -> @@ -611,8 +611,7 @@ do_handle_call({pread,Async,Handle,At,Length}, From, State) -> fun({ok,Data}, State2) -> case get_mode(Handle, State2) of binary -> {{ok,Data}, State2}; - text -> - {{ok,unicode:characters_to_list(Data)}, State2} + text -> {{ok,binary_to_list(Data)}, State2} end; (Rep, State2) -> {Rep, State2} diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index f4e6a23a1e..38a0b7ec7c 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -39,7 +39,7 @@ key_exchange_init_msg/1, key_init/3, new_keys_message/1, handle_kexinit_msg/3, handle_kexdh_init/2, - handle_kex_dh_gex_group/2, handle_kex_dh_gex_reply/2, + handle_kex_dh_gex_group/2, handle_kex_dh_gex_init/2, handle_kex_dh_gex_reply/2, handle_new_keys/2, handle_kex_dh_gex_request/2, handle_kexdh_reply/2, unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1, @@ -66,6 +66,8 @@ algo_classes() -> [kex, public_key, cipher, mac, compression]. default_algorithms(compression) -> %% Do not announce '[email protected]' because there seem to be problems supported_algorithms(compression, same(['[email protected]'])); +default_algorithms(kex) -> + supported_algorithms(kex, []); default_algorithms(Alg) -> supported_algorithms(Alg). @@ -73,23 +75,29 @@ default_algorithms(Alg) -> supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. supported_algorithms(kex) -> - ['diffie-hellman-group1-sha1']; + select_crypto_supported( + [{'diffie-hellman-group14-sha1', [{hashs,sha}]}, + {'diffie-hellman-group1-sha1', [{hashs,sha}]}, + {'diffie-hellman-group-exchange-sha256', [{hashs,sha256}]}, + {'diffie-hellman-group-exchange-sha1', [{hashs,sha}]} + ]); supported_algorithms(public_key) -> ssh_auth:default_public_key_algorithms(); supported_algorithms(cipher) -> - Supports = crypto:supports(), - CipherAlgos = [{aes_ctr, 'aes128-ctr'}, {aes_cbc128, 'aes128-cbc'}, {des3_cbc, '3des-cbc'}], - Algs = [SshAlgo || - {CryptoAlgo, SshAlgo} <- CipherAlgos, - lists:member(CryptoAlgo, proplists:get_value(ciphers, Supports, []))], - same(Algs); + same( + select_crypto_supported( + [{'aes128-ctr', [{ciphers,aes_ctr}]}, + {'aes128-cbc', [{ciphers,aes_cbc128}]}, + {'3des-cbc', [{ciphers,des3_cbc}]} + ] + )); supported_algorithms(mac) -> - Supports = crypto:supports(), - HashAlgos = [{sha256, 'hmac-sha2-256'}, {sha, 'hmac-sha1'}], - Algs = [SshAlgo || - {CryptoAlgo, SshAlgo} <- HashAlgos, - lists:member(CryptoAlgo, proplists:get_value(hashs, Supports, []))], - same(Algs); + same( + select_crypto_supported( + [{'hmac-sha2-256', [{hashs,sha256}]}, + {'hmac-sha1', [{hashs,sha}]} + ] + )); supported_algorithms(compression) -> same(['none','zlib','[email protected]']). @@ -100,7 +108,15 @@ supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) -> supported_algorithms(Key, BlackList) -> supported_algorithms(Key) -- BlackList. - +select_crypto_supported(L) -> + Sup = crypto:supports(), + [Name || {Name,CryptoRequires} <- L, + crypto_supported(CryptoRequires, Sup)]. + +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}]. @@ -135,7 +151,7 @@ ssh_vsn() -> _:_ -> "" end. -random_id(Nlo, Nup) -> +random_id(Nlo, Nup) -> [crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ]. hello_version_msg(Data) -> @@ -144,7 +160,7 @@ hello_version_msg(Data) -> next_seqnum(SeqNum) -> (SeqNum + 1) band 16#ffffffff. -decrypt_first_block(Bin, #ssh{decrypt_block_size = BlockSize} = Ssh0) -> +decrypt_first_block(Bin, #ssh{decrypt_block_size = BlockSize} = Ssh0) -> <<EncBlock:BlockSize/binary, EncData/binary>> = Bin, {Ssh, <<?UINT32(PacketLen), _/binary>> = DecData} = decrypt(Ssh0, EncBlock), @@ -246,52 +262,83 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, Ssh0#ssh{algorithms = Algoritms}); _ -> %% TODO: Correct code? - throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, description = "Selection of key exchange" " algorithm failed", - language = "en"}) + language = ""}) end; handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own, #ssh{role = server} = Ssh) -> {ok, Algoritms} = select_algorithm(server, CounterPart, Own), - {ok, Ssh#ssh{algorithms = Algoritms}}. + case verify_algorithm(Algoritms) of + true -> + {ok, Ssh#ssh{algorithms = Algoritms}}; + _ -> + throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Selection of key exchange" + " algorithm failed", + language = ""}) + end. %% TODO: diffie-hellman-group14-sha1 should also be supported. %% Maybe check more things ... -verify_algorithm(#alg{kex = 'diffie-hellman-group1-sha1'}) -> - true; -verify_algorithm(#alg{kex = 'diffie-hellman-group-exchange-sha1'}) -> - true; -verify_algorithm(_) -> - false. -key_exchange_first_msg('diffie-hellman-group1-sha1', Ssh0) -> - {G, P} = dh_group1(), +verify_algorithm(#alg{kex = undefined}) -> false; +verify_algorithm(#alg{hkey = undefined}) -> false; +verify_algorithm(#alg{send_mac = undefined}) -> false; +verify_algorithm(#alg{recv_mac = undefined}) -> false; +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 = 'diffie-hellman-group1-sha1'}) -> true; +verify_algorithm(#alg{kex = 'diffie-hellman-group14-sha1'}) -> true; +verify_algorithm(#alg{kex = 'diffie-hellman-group-exchange-sha1'}) -> true; +verify_algorithm(#alg{kex = 'diffie-hellman-group-exchange-sha256'}) -> true; +verify_algorithm(_) -> false. + +%%%---------------------------------------------------------------- +%%% +%%% Key exchange initialization +%%% +key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; + Kex == 'diffie-hellman-group14-sha1' -> + {G, P} = dh_group(Kex), {Private, Public} = dh_gen_key(G, P, 1024), + %% Public = G^Private mod P (def) {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; -key_exchange_first_msg('diffie-hellman-group-exchange-sha1', Ssh0) -> +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, {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, - n = NBits, max = Max}, + n = NBits, + max = Max}, Ssh0), {ok, SshPacket, Ssh1#ssh{keyex_info = {Min, Max, NBits}}}. - -handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0) -> - {G, P} = dh_group1(), +%%%---------------------------------------------------------------- +%%% +%%% diffie-hellman-group1-sha1 +%%% diffie-hellman-group14-sha1 +%%% +handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, + Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) -> + %% server + {G, P} = dh_group(Kex), if 1=<E, E=<(P-1) -> {Private, Public} = dh_gen_key(G, P, 1024), - K = ssh_math:ipow(E, Private, P), + K = dh_compute_key(G, P, E, Private), Key = get_host_key(Ssh0), H = kex_h(Ssh0, Key, E, Public, K), H_SIG = sign_host_key(Ssh0, Key, H), @@ -299,27 +346,173 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0) -> f = Public, h_sig = H_SIG }, Ssh0), - {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}, shared_secret = K, exchanged_hash = H, session_id = sid(Ssh1, H)}}; + true -> - Error = {error,bad_e_from_peer}, - Disconnect = #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'f' out of bounds", - language = "en"}, - throw({Error, Disconnect}) + throw({{error,bad_e_from_peer}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'e' out of bounds", + language = ""} + }) end. +handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, + f = F, + h_sig = H_SIG}, + #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) -> + %% client + if + 1=<F, F=<(P-1)-> + K = dh_compute_key(G, P, F, Private), + H = kex_h(Ssh0, HostKey, Public, F, K), + + case verify_host_key(Ssh0, HostKey, H, H_SIG) of + ok -> + {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), + {ok, SshPacket, Ssh#ssh{shared_secret = K, + exchanged_hash = H, + session_id = sid(Ssh, H)}}; + Error -> + throw({Error, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed", + language = "en"} + }) + end; + + true -> + throw({{error,bad_f_from_peer}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'f' out of bounds", + language = ""} + }) + end. + + +%%%---------------------------------------------------------------- +%%% +%%% diffie-hellman-group-exchange-sha1 +%%% +handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min, + n = NBits, + max = Max}, + Ssh0=#ssh{opts=Opts}) when Min=<NBits, NBits=<Max -> + %% server + {G, P} = dh_gex_group(Min, NBits, Max, proplists:get_value(dh_gex_groups,Opts)), + {Private, Public} = dh_gen_key(G, P, 1024), + {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} + }}; +handle_kex_dh_gex_request(_, _) -> + throw({{error,bad_ssh_msg_kex_dh_gex_request}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, bad values in ssh_msg_kex_dh_gex_request", + language = ""} + }). + handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) -> - {Private, Public} = dh_gen_key(G,P,1024), + %% client + {Private, Public} = dh_gen_key(G, P, 1024), {SshPacket, Ssh1} = - ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), + ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def) + {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}. +handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E}, + #ssh{keyex_key = {{Private, Public}, {G, P}}, + keyex_info = {Min, Max, NBits}} = + Ssh0) -> + %% server + if + 1=<E, E=<(P-1) -> + K = dh_compute_key(G, P, E, Private), + 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), + {SshPacket, Ssh} = + ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey, + f = Public, + h_sig = H_SIG}, Ssh0), + {ok, SshPacket, Ssh#ssh{shared_secret = K, + exchanged_hash = H, + session_id = sid(Ssh, H) + }}; + true -> + throw({{error,bad_K}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'K' out of bounds", + language = ""} + }) + end; + true -> + throw({{error,bad_e_from_peer}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'e' out of bounds", + language = ""} + }) + end. + +handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey, + f = F, + h_sig = H_SIG}, + #ssh{keyex_key = {{Private, Public}, {G, P}}, + keyex_info = {Min, Max, NBits}} = + Ssh0) -> + %% client + if + 1=<F, F=<(P-1)-> + K = dh_compute_key(G, P, F, Private), + if + 1<K, K<(P-1) -> + H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, Public, F, K), + + case verify_host_key(Ssh0, HostKey, H, H_SIG) of + ok -> + {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), + {ok, SshPacket, Ssh#ssh{shared_secret = K, + exchanged_hash = H, + session_id = sid(Ssh, H)}}; + _Error -> + throw(#ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed", + language = ""} + ) + end; + + true -> + throw({{error,bad_K}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'K' out of bounds", + language = ""} + }) + end; + true -> + throw({{error,bad_f_from_peer}, + #ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + description = "Key exchange failed, 'f' out of bounds", + language = ""} + }) + end. + +%%%---------------------------------------------------------------- handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> try install_alg(Ssh0) of #ssh{} = Ssh -> @@ -331,69 +524,6 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) -> language = "en"}) end. - -%% %% Select algorithms -handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, f = F, - h_sig = H_SIG}, - #ssh{keyex_key = {{Private, Public}, {_G, P}}} = Ssh0) when 1=<F, F=<(P-1)-> - K = ssh_math:ipow(F, Private, P), - H = kex_h(Ssh0, HostKey, Public, F, K), - - case verify_host_key(Ssh0, HostKey, H, H_SIG) of - ok -> - {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, - exchanged_hash = H, - session_id = sid(Ssh, H)}}; - Error -> - Disconnect = #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed", - language = "en"}, - throw({Error, Disconnect}) - end; -handle_kexdh_reply(#ssh_msg_kexdh_reply{}, _SSH) -> - Error = {error,bad_f_from_peer}, - Disconnect = #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed, 'f' out of bounds", - language = "en"}, - throw({Error, Disconnect}). - - -handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = _Min, - n = _NBits, - max = _Max}, Ssh0) -> - {G,P} = dh_group1(), %% TODO real imp this seems to be a hack?! - {Private, Public} = dh_gen_key(G, P, 1024), - {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}}}}. - -handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey, - f = F, - h_sig = H_SIG}, - #ssh{keyex_key = {{Private, Public}, {G, P}}, - keyex_info = {Min, Max, NBits}} = - Ssh0) -> - K = ssh_math:ipow(F, Private, P), - H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, Public, F, K), - - case verify_host_key(Ssh0, HostKey, H, H_SIG) of - ok -> - {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0), - {ok, SshPacket, Ssh#ssh{shared_secret = K, - exchanged_hash = H, - session_id = sid(Ssh, H)}}; - _Error -> - Disconnect = #ssh_msg_disconnect{ - code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - description = "Key exchange failed", - language = "en"}, - throw(Disconnect) - end. - %% select session id sid(#ssh{session_id = undefined}, H) -> H; @@ -496,7 +626,6 @@ select_algorithm(Role, Client, Server) -> decompress = Decompression, c_lng = C_Lng, s_lng = S_Lng}, -%%ct:pal("~p~n Client=~p~n Server=~p~n Alg=~p~n",[Role,Client,Server,Alg]), {ok, Alg}. select_encrypt_decrypt(client, Client, Server) -> @@ -691,6 +820,7 @@ verify(PlainText, Hash, Sig, Key) -> %% key exchange %% %% diffie-hellman-group1-sha1 REQUIRED +%% diffie-hellman-group14-sha1 REQUIRED %% %% @@ -986,7 +1116,7 @@ recv_mac_init(SSH) -> recv_mac_final(SSH) -> {ok, SSH#ssh { recv_mac = none, recv_mac_key = undefined }}. -mac(none, _ , _, _) -> +mac(none, _ , _, _) -> <<>>; mac('hmac-sha1', Key, SeqNum, Data) -> crypto:hmac(sha, Key, [<<?UINT32(SeqNum)>>, Data]); @@ -1005,8 +1135,12 @@ hash(SSH, Char, Bits) -> case SSH#ssh.kex of 'diffie-hellman-group1-sha1' -> fun(Data) -> crypto:hash(sha, Data) end; + 'diffie-hellman-group14-sha1' -> + fun(Data) -> crypto:hash(sha, Data) end; 'diffie-hellman-group-exchange-sha1' -> fun(Data) -> crypto:hash(sha, Data) end; + 'diffie-hellman-group-exchange-sha256' -> + fun(Data) -> crypto:hash(sha256, Data) end; _ -> exit({bad_algorithm,SSH#ssh.kex}) end, @@ -1056,8 +1190,11 @@ kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) -> ssh_message:encode_host_key(Key), Min, NBits, Max, Prime, Gen, E,F,K], Ts) end, - crypto:hash(sha,L). + crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L). +sha('diffie-hellman-group-exchange-sha1') -> sha; +sha('diffie-hellman-group-exchange-sha256') -> sha256. + mac_key_size('hmac-sha1') -> 20*8; mac_key_size('hmac-sha1-96') -> 20*8; mac_key_size('hmac-md5') -> 16*8; @@ -1081,13 +1218,63 @@ peer_name({Host, _}) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -dh_group1() -> - {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}. +dh_group('diffie-hellman-group1-sha1') -> ?dh_group1; +dh_group('diffie-hellman-group14-sha1') -> ?dh_group14. + +dh_gex_default_groups() -> + [{1024, ?dh_group1 }, + {2048, ?dh_group14}, + {3072, ?dh_group15}, + {4096, ?dh_group16}]. + + +dh_gex_group(Min, N, Max, undefined) -> + dh_gex_group(Min, N, Max, dh_gex_default_groups()); +dh_gex_group(Min, N, Max, Groups) -> + %% First try to find an exact match. If not an exact match, select the largest possible. + {_,Group} = + lists:foldl( + fun(_, {I,G}) when I==N -> + %% If we have an exact match already: use that one + {I,G}; + ({I,G}, _) when I==N -> + %% If we now found an exact match: use that very one + {I,G}; + ({I,G}, {Imax,_Gmax}) when Min=<I,I=<Max, % a) {I,G} fullfills the requirements + I>Imax -> % b) {I,G} is larger than current max + %% A group within the limits and better than the one we have + {I,G}; + (_, IGmax) -> + %% Keep the one we have + IGmax + end, {-1,undefined}, Groups), + + case Group of + undefined -> + throw(#ssh_msg_disconnect{ + code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "No possible diffie-hellman-group-exchange group found", + language = ""}); + _ -> + Group + end. + dh_gen_key(G, P, _) -> {Public, Private} = crypto:generate_key(dh, [P, G]), {crypto:bytes_to_integer(Private), crypto:bytes_to_integer(Public)}. +dh_compute_key(G, P, OthersPublic, MyPrivate) -> + crypto:bytes_to_integer( + crypto:compute_key(dh, OthersPublic, MyPrivate, [P,G]) + ). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% Other utils +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + trim_tail(Str) -> lists:reverse(trim_head(lists:reverse(Str))). diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 2faf8a9316..9e1de171c2 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -188,49 +188,24 @@ -define(SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 14). -define(SSH_DISCONNECT_ILLEGAL_USER_NAME, 15). +%% groups + +%%% rfc 2489, ch 6.2 +-define(dh_group1, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}). + +%%% rfc 3526, ch3 +-define(dh_group14, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}). + +%%% rfc 3526, ch4 +-define(dh_group15, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF}). + +%%% rfc 3526, ch5 +-define(dh_group16, + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF}). + -%%%---------------------------------------------------------------------- -%%% # DH_14_xxx -%%% Description: Oakley group 14 prime numbers and generator. Used in -%%% diffie-hellman-group1-sha1 key exchange method. -%%%---------------------------------------------------------------------- -%%%---------------------------------------------------------------------- -%%% # DH_14_P -%%% Description: Prime for this group -%%%---------------------------------------------------------------------- - --define(DH_14_P, - <<000,000,000,129,000,255,255,255,255,255,255,255,255,201,015,218, - 162,033,104,194,052,196,198,098,139,128,220,028,209,041,002,078, - 008,138,103,204,116,002,011,190,166,059,019,155,034,081,074,008, - 121,142,052,004,221,239,149,025,179,205,058,067,027,048,043,010, - 109,242,095,020,055,079,225,053,109,109,081,194,069,228,133,181, - 118,098,094,126,198,244,076,066,233,166,055,237,107,011,255,092, - 182,244,006,183,237,238,056,107,251,090,137,159,165,174,159,036, - 017,124,075,031,230,073,040,102,081,236,230,083,129,255,255,255, - 255,255,255,255,255>>). - -%%%---------------------------------------------------------------------- -%%% # DH_14_G -%%% Description: Generator for DH_14_P. -%%%---------------------------------------------------------------------- - --define(DH_14_G, <<0,0,0,1,2>>). - -%%%---------------------------------------------------------------------- -%%% # DH_14_Q -%%% Description: Group order (DH_14_P - 1) / 2. -%%%---------------------------------------------------------------------- - --define(DH_14_Q, - <<000,000,000,128,127,255,255,255,255,255,255,255,228,135,237,081, - 016,180,097,026,098,099,049,069,192,110,014,104,148,129,039,004, - 069,051,230,058,001,005,223,083,029,137,205,145,040,165,004,060, - 199,026,002,110,247,202,140,217,230,157,033,141,152,021,133,054, - 249,047,138,027,167,240,154,182,182,168,225,034,242,066,218,187, - 049,047,063,099,122,038,033,116,211,027,246,181,133,255,174,091, - 122,003,091,246,247,028,053,253,173,068,207,210,215,079,146,008, - 190,037,143,243,036,148,051,040,246,115,041,192,255,255,255,255, - 255,255,255,255>>). -endif. % -ifdef(ssh_transport). |