diff options
author | Hans Nilsson <[email protected]> | 2015-08-04 10:56:26 +0200 |
---|---|---|
committer | Hans Nilsson <[email protected]> | 2015-08-04 10:56:26 +0200 |
commit | e05f0b617f85547cc3c537dda3678d9c9a0e80c1 (patch) | |
tree | f985063dcc7d3572873906a78239c3fc33215f72 /lib | |
parent | 98647fcc1632f60871adee20031e294e5d5b6eb0 (diff) | |
parent | 481eb2c445eeb350cd370aa4880e7c72c5173cfc (diff) | |
download | otp-e05f0b617f85547cc3c537dda3678d9c9a0e80c1.tar.gz otp-e05f0b617f85547cc3c537dda3678d9c9a0e80c1.tar.bz2 otp-e05f0b617f85547cc3c537dda3678d9c9a0e80c1.zip |
Merge branch 'hans/ssh/diffie-hellman-group-exchange-shaN/OTP-12672' into maint
* hans/ssh/diffie-hellman-group-exchange-shaN/OTP-12672:
ssh: added more groups for gex
ssh: rm ssh_math and use crypto:compute_key instead
ssh: filter algs unknown to crypto (except pk)
ssh: add test group
ssh: Add experimental 'diffie-hellman-group-exchange-sha256 support
ssh: Repair/add experimental diffie-hellman-group-exchange-sha1 support
Diffstat (limited to 'lib')
-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_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_transport.erl | 341 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.hrl | 45 | ||||
-rw-r--r-- | lib/ssh/test/ssh_basic_SUITE.erl | 51 | ||||
-rw-r--r-- | lib/ssh/test/ssh_sftp_SUITE.erl | 87 |
9 files changed, 402 insertions, 225 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_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_transport.erl b/lib/ssh/src/ssh_transport.erl index 2e7391e1f8..9be8e45aed 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,11 @@ 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) -> + %% Do not announce the experimental 'diffie-hellman-group-exchange-sha*' yet + supported_algorithms(kex, ['diffie-hellman-group-exchange-sha1', + 'diffie-hellman-group-exchange-sha256' + ]); default_algorithms(Alg) -> supported_algorithms(Alg). @@ -73,23 +78,28 @@ default_algorithms(Alg) -> supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. supported_algorithms(kex) -> - ['diffie-hellman-group1-sha1']; + select_crypto_supported( + [{'diffie-hellman-group1-sha1', [{hashs,sha}]}, + {'diffie-hellman-group-exchange-sha1', [{hashs,sha}]}, + {'diffie-hellman-group-exchange-sha256', [{hashs,sha256}]} + ]); 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 +110,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 +153,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 +162,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), @@ -280,33 +298,45 @@ verify_algorithm(#alg{decompress = undefined}) -> false; verify_algorithm(#alg{kex = 'diffie-hellman-group1-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('diffie-hellman-group1-sha1', Ssh0) -> {G, P} = dh_group1(), {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}}}. - +%%%---------------------------------------------------------------- +%%% +%%% diffie-hellman-group1-sha1 +%%% handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0) -> + %% server {G, P} = dh_group1(), 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), @@ -314,27 +344,172 @@ 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) when Min=<NBits, NBits=<Max -> + %% server + {G, P} = dh_gex_group(Min, NBits, Max), + {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 -> @@ -346,69 +521,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; @@ -511,7 +623,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) -> @@ -1001,7 +1112,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]); @@ -1022,6 +1133,8 @@ hash(SSH, Char, Bits) -> 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, @@ -1071,8 +1184,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; @@ -1096,13 +1212,46 @@ peer_name({Host, _}) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% rfc 2489, ch 6.2 dh_group1() -> {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}. +%%% rfc 3526, ch3 +dh_group14() -> + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}. + +%%% rfc 3526, ch4 +dh_group15() -> + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF}. + +%%% rfc 3526, ch5 +dh_group16() -> + {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF}. + + +%%% First try exact match: +dh_gex_group(_Min, N, _Max) when N==1024 -> dh_group1(); +dh_gex_group(_Min, N, _Max) when N==2048 -> dh_group14(); +dh_gex_group(_Min, N, _Max) when N==3072 -> dh_group15(); +dh_gex_group(_Min, N, _Max) when N==4096 -> dh_group16(); +%%% If not an exact match, select the largest possible: +dh_gex_group(Min, _N, Max) when Min=<4096, 4096=<Max -> dh_group16(); +dh_gex_group(Min, _N, Max) when Min=<3072, 3072=<Max -> dh_group15(); +dh_gex_group(Min, _N, Max) when Min=<2048, 2048=<Max -> dh_group14(); +dh_gex_group(Min, _N, Max) when Min=<1024, 1024=<Max -> dh_group1(). + + 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]) + ). + + + 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..ab59742b96 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -188,49 +188,4 @@ -define(SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 14). -define(SSH_DISCONNECT_ILLEGAL_USER_NAME, 15). - -%%%---------------------------------------------------------------------- -%%% # 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). diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index bf9fe54c57..c71463db30 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -42,6 +42,7 @@ suite() -> all() -> [app_test, appup_test, + {group, 'diffie-hellman-group-exchange-sha1'}, {group, dsa_key}, {group, rsa_key}, {group, dsa_pass_key}, @@ -92,6 +93,8 @@ groups() -> max_sessions_sftp_start_channel_parallel, max_sessions_sftp_start_channel_sequential ]}, + {'diffie-hellman-group-exchange-sha1', [], ['diffie-hellman-group-exchange-sha1' + ]}, {dir_options, [], [user_dir_option, system_dir_option]} ]. @@ -146,6 +149,17 @@ init_per_group(internal_error, Config) -> ssh_test_lib:setup_dsa(DataDir, PrivDir), file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")), Config; +init_per_group('diffie-hellman-group-exchange-sha1', Config) -> + case lists:member('diffie-hellman-group-exchange-sha1', + ssh_transport:supported_algorithms(kex)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_rsa(DataDir, PrivDir), + Config; + false -> + {skip,"diffie-hellman-group-exchange-sha1 is not supported"} + end; init_per_group(dir_options, Config) -> PrivDir = ?config(priv_dir, Config), %% Make unreadable dir: @@ -818,6 +832,43 @@ ssh_msg_debug_fun_option_client(Config) -> end. %%-------------------------------------------------------------------- +'diffie-hellman-group-exchange-sha1'(Config) -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"foo", "bar"}]}, + {preferred_algorithms, + [{kex, ['diffie-hellman-group-exchange-sha1']}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "bar"}, + {user_dir, UserDir}, + {preferred_algorithms, + [{kex, ['diffie-hellman-group-exchange-sha1']}]}, + {user_interaction, false}]), + check(ConnectionRef, Pid). + +check(ConnectionRef, Pid) -> + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:exec(ConnectionRef, ChannelId, + "1+1.", infinity), + Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2\n">>}}, + case ssh_test_lib:receive_exec_result(Data) of + expected -> + ok; + Other -> + ct:fail(Other) + end, + ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- connectfun_disconnectfun_server(Config) -> PrivDir = ?config(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index 6c631e6f6e..a95e16a0b3 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -64,12 +64,20 @@ end_per_suite(Config) -> groups() -> [{not_unicode, [], [{group,erlang_server}, {group,openssh_server}, + {group,'diffie-hellman-group-exchange-sha1'}, + {group,'diffie-hellman-group-exchange-sha256'}, sftp_nonexistent_subsystem]}, {unicode, [], [{group,erlang_server}, {group,openssh_server}, sftp_nonexistent_subsystem]}, + {'diffie-hellman-group-exchange-sha1', [], [{group,erlang_server}, + {group,openssh_server}]}, + + {'diffie-hellman-group-exchange-sha256', [], [{group,erlang_server}, + {group,openssh_server}]}, + {erlang_server, [], [{group,write_read_tests}, version_option, {group,remote_tar}]}, @@ -142,22 +150,26 @@ init_per_group(erlang_server, Config) -> User = ?config(user, Config), Passwd = ?config(passwd, Config), Sftpd = {_, HostX, PortX} = - ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, PrivDir}, - {user_passwords, - [{User, Passwd}]}]), + ssh_test_lib:daemon(extra_opts(Config) ++ + [{system_dir, SysDir}, + {user_dir, PrivDir}, + {user_passwords, + [{User, Passwd}]}]), [{peer, {fmt_host(HostX),PortX}}, {group, erlang_server}, {sftpd, Sftpd} | Config]; init_per_group(openssh_server, Config) -> ct:comment("Begin ~p",[grps(Config)]), Host = ssh_test_lib:hostname(), - case (catch ssh_sftp:start_channel(Host, - [{user_interaction, false}, - {silently_accept_hosts, true}])) of + case (catch ssh_sftp:start_channel(Host, + extra_opts(Config) ++ + [{user_interaction, false}, + {silently_accept_hosts, true}])) of {ok, _ChannelPid, Connection} -> [{peer, {_HostName,{IPx,Portx}}}] = ssh:connection_info(Connection,[peer]), ssh:close(Connection), [{peer, {fmt_host(IPx),Portx}}, {group, openssh_server} | Config]; + {error,"Key exchange failed"} -> + {skip, "openssh server doesn't support the tested kex algorithm"}; _ -> {skip, "No openssh server"} end; @@ -172,10 +184,11 @@ init_per_group(remote_tar, Config) -> case ?config(group, Config) of erlang_server -> ssh:connect(Host, Port, - [{user, User}, - {password, Passwd}, - {user_interaction, false}, - {silently_accept_hosts, true}]); + extra_opts(Config) ++ + [{user, User}, + {password, Passwd}, + {user_interaction, false}, + {silently_accept_hosts, true}]); openssh_server -> ssh:connect(Host, Port, [{user_interaction, false}, @@ -184,6 +197,28 @@ init_per_group(remote_tar, Config) -> [{remote_tar, true}, {connection, Connection} | Config]; +init_per_group('diffie-hellman-group-exchange-sha1', Config) -> + case lists:member('diffie-hellman-group-exchange-sha1', + ssh_transport:supported_algorithms(kex)) of + true -> + [{extra_opts, [{preferred_algorithms, [{kex,['diffie-hellman-group-exchange-sha1']}]}]} + | Config]; + + false -> + {skip,"'diffie-hellman-group-exchange-sha1' not supported by this version of erlang ssh"} + end; + +init_per_group('diffie-hellman-group-exchange-sha256', Config) -> + case lists:member('diffie-hellman-group-exchange-sha256', + ssh_transport:supported_algorithms(kex)) of + true -> + [{extra_opts, [{preferred_algorithms, [{kex,['diffie-hellman-group-exchange-sha256']}]}]} + | Config]; + + false -> + {skip,"'diffie-hellman-group-exchange-sha256' not supported by this version of erlang ssh"} + end; + init_per_group(write_read_tests, Config) -> ct:comment("Begin ~p",[grps(Config)]), Config. @@ -194,7 +229,6 @@ grps(Config) -> lists:flatten([proplists:get_value(tc_group_properties,Config,[]), proplists:get_value(tc_group_path,Config,[])])). - end_per_group(erlang_server, Config) -> ct:comment("End ~p",[grps(Config)]), Config; @@ -227,11 +261,12 @@ init_per_testcase(version_option, Config) -> Passwd = ?config(passwd, Config), {ok, ChannelPid, Connection} = ssh_sftp:start_channel(Host, Port, - [{sftp_vsn, 3}, - {user, User}, - {password, Passwd}, - {user_interaction, false}, - {silently_accept_hosts, true}]), + extra_opts(Config) ++ + [{sftp_vsn, 3}, + {user, User}, + {password, Passwd}, + {user_interaction, false}, + {silently_accept_hosts, true}]), Sftp = {ChannelPid, Connection}, [{sftp,Sftp}, {watchdog, Dog} | TmpConfig]; @@ -249,10 +284,12 @@ init_per_testcase(Case, Config0) -> {_,Host, Port} = ?config(sftpd, Config2), {ok, ChannelPid, Connection} = ssh_sftp:start_channel(Host, Port, - [{user, User}, - {password, Passwd}, - {user_interaction, false}, - {silently_accept_hosts, true}]), + extra_opts(Config2) ++ + [{user, User}, + {password, Passwd}, + {user_interaction, false}, + {silently_accept_hosts, true}] + ), Sftp = {ChannelPid, Connection}, [{sftp, Sftp}, {watchdog, Dog} | Config2]; openssh_server when Case == links -> @@ -261,8 +298,9 @@ init_per_testcase(Case, Config0) -> Host = ssh_test_lib:hostname(), {ok, ChannelPid, Connection} = ssh_sftp:start_channel(Host, - [{user_interaction, false}, - {silently_accept_hosts, true}]), + extra_opts(Config2) ++ + [{user_interaction, false}, + {silently_accept_hosts, true}]), Sftp = {ChannelPid, Connection}, [{sftp, Sftp}, {watchdog, Dog} | Config2] end, @@ -910,7 +948,8 @@ prep(Config) -> ok = file:write_file_info(TestFile, FileInfo#file_info{mode = Mode}). - +extra_opts(Config) -> + proplists:get_value(extra_opts, Config, []). chk_tar(Items, Config) -> chk_tar(Items, Config, []). |