diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/ssh.hrl | 31 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 15 | ||||
-rw-r--r-- | lib/ssh/src/ssh_dbg.erl | 116 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 8 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftp.erl | 13 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftpd.erl | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 234 | ||||
-rw-r--r-- | lib/ssh/src/ssh_xfer.erl | 2 |
8 files changed, 340 insertions, 85 deletions
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 9631427749..66dbf0b144 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -118,6 +118,9 @@ 'diffie-hellman-group14-sha256' | 'diffie-hellman-group16-sha512' | 'diffie-hellman-group18-sha512' | + 'curve25519-sha256' | + '[email protected]' | + 'curve448-sha512' | 'ecdh-sha2-nistp256' | 'ecdh-sha2-nistp384' | 'ecdh-sha2-nistp521' @@ -140,7 +143,8 @@ 'aes192-ctr' | 'aes256-ctr' | + '[email protected]' | . -type mac_alg() :: 'AEAD_AES_128_GCM' | @@ -492,4 +496,29 @@ -define(wr_record(N), ?wr_record(N, [])). +%% Circular trace buffer macros + +-record(circ_buf_entry, + { + module, + line, + function, + pid = self(), + value + }). + +-define(CIRC_BUF_IN(VALUE), + ssh_dbg:cbuf_in( + #circ_buf_entry{module = ?MODULE, + line = ?LINE, + function = {?FUNCTION_NAME,?FUNCTION_ARITY}, + pid = self(), + value = (VALUE) + }) + ). + +-define(CIRC_BUF_IN_ONCE(VALUE), + ((fun(V) -> ?CIRC_BUF_IN(V), V end)(VALUE)) + ). + -endif. % SSH_HRL defined diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8e4831a601..4b41c10cbb 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -356,6 +356,8 @@ alg(ConnectionHandler) -> | undefined, encrypted_data_buffer = <<>> :: binary() | undefined, + aead_data = <<>> :: binary() + | undefined, undecrypted_packet_length :: undefined | non_neg_integer(), key_exchange_init_msg :: #ssh_msg_kexinit{} | undefined, @@ -1308,14 +1310,16 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, try ssh_transport:handle_packet_part( D0#data.decrypted_data_buffer, <<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>, - D0#data.undecrypted_packet_length, + D0#data.aead_data, + D0#data.undecrypted_packet_length, D0#data.ssh_params) of {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} -> D1 = D0#data{ssh_params = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)}, decrypted_data_buffer = <<>>, - undecrypted_packet_length = undefined, + undecrypted_packet_length = undefined, + aead_data = <<>>, encrypted_data_buffer = EncryptedDataRest}, try ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1)) @@ -1353,14 +1357,15 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock, StateName, D1), {stop, Shutdown, D} end; - - {get_more, DecryptedBytes, EncryptedDataRest, RemainingSshPacketLen, Ssh1} -> + + {get_more, DecryptedBytes, EncryptedDataRest, AeadData, RemainingSshPacketLen, Ssh1} -> %% Here we know that there are not enough bytes in %% EncryptedDataRest to use. We must wait for more. inet:setopts(Sock, [{active, once}]), {keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest, decrypted_data_buffer = DecryptedBytes, - undecrypted_packet_length = RemainingSshPacketLen, + undecrypted_packet_length = RemainingSshPacketLen, + aead_data = AeadData, ssh_params = Ssh1}}; {bad_mac, Ssh1} -> diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index b53c09b17d..4fe15b24d3 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -54,7 +54,13 @@ start_tracer/0, start_tracer/1, on/1, on/0, off/1, off/0, - go_on/0 + go_on/0, + %% Circular buffer + cbuf_start/0, cbuf_start/1, + cbuf_stop_clear/0, + cbuf_in/1, + cbuf_list/0, + fmt_cbuf_items/0, fmt_cbuf_item/1 ]). -export([shrink_bin/1, @@ -71,6 +77,8 @@ -behaviour(gen_server). -define(SERVER, ?MODULE). +-define(CALL_TIMEOUT, 15000). % 3x the default + %%%================================================================ -define(ALL_DBG_TYPES, get_all_dbg_types()). @@ -107,7 +115,7 @@ start_tracer(WriteFun) when is_function(WriteFun,3) -> start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) -> Handler = fun(Arg, Acc0) -> - try_all_types_in_all_modules(gen_server:call(?SERVER, get_on), + try_all_types_in_all_modules(gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT), Arg, WriteFun, Acc0) end, @@ -122,7 +130,7 @@ off() -> off(?ALL_DBG_TYPES). % A bit overkill... off(Type) -> switch(off, Type). go_on() -> - IsOn = gen_server:call(?SERVER, get_on), + IsOn = gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT), on(IsOn). %%%---------------------------------------------------------------- @@ -253,7 +261,7 @@ switch(X, Types) when is_list(Types) -> end, case lists:usort(Types) -- ?ALL_DBG_TYPES of [] -> - gen_server:call(?SERVER, {switch,X,Types}); + gen_server:call(?SERVER, {switch,X,Types}, ?CALL_TIMEOUT); L -> {error, {unknown, L}} end. @@ -331,3 +339,103 @@ ts({_,_,Usec}=Now) when is_integer(Usec) -> io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); ts(_) -> "-". + +%%%================================================================ +-define(CIRC_BUF, circ_buf). + +cbuf_start() -> + cbuf_start(20). + +cbuf_start(CbufMaxLen) -> + put(?CIRC_BUF, {CbufMaxLen,queue:new()}), + ok. + + +cbuf_stop_clear() -> + case erase(?CIRC_BUF) of + undefined -> + []; + {_CbufMaxLen,Queue} -> + queue:to_list(Queue) + end. + + +cbuf_in(Value) -> + case get(?CIRC_BUF) of + undefined -> + disabled; + {CbufMaxLen,Queue} -> + UpdatedQueue = + try queue:head(Queue) of + {Value, TS0, Cnt0} -> + %% Same Value as last saved in the queue + queue:in_r({Value, TS0, Cnt0+1}, + queue:drop(Queue) + ); + _ -> + queue:in_r({Value, erlang:timestamp(), 1}, + truncate_cbuf(Queue, CbufMaxLen) + ) + catch + error:empty -> + queue:in_r({Value, erlang:timestamp(), 1}, Queue) + end, + put(?CIRC_BUF, {CbufMaxLen,UpdatedQueue}), + ok + end. + + +cbuf_list() -> + case get(?CIRC_BUF) of + undefined -> + []; + {_CbufMaxLen,Queue} -> + queue:to_list(Queue) + end. + + +truncate_cbuf(Q, CbufMaxLen) -> + case queue:len(Q) of + N when N>=CbufMaxLen -> + truncate_cbuf(element(2,queue:out_r(Q)), CbufMaxLen); + _ -> + Q + end. + +fmt_cbuf_items() -> + lists:flatten( + io_lib:format("Circular trace buffer. Latest item first.~n~s~n", + [case get(?CIRC_BUF) of + {Max,_} -> + L = cbuf_list(), + [io_lib:format("==== ~.*w: ~s~n",[num_digits(Max),N,fmt_cbuf_item(X)]) || + {N,X} <- lists:zip(lists:seq(1,length(L)), L) + ]; + _ -> + io_lib:format("Not started.~n",[]) + end])). + + +num_digits(0) -> 1; +num_digits(N) when N>0 -> 1+trunc(math:log10(N)). + + +fmt_cbuf_item({Value, TimeStamp, N}) -> + io_lib:format("~s~s~n~s~n", + [fmt_ts(TimeStamp), + [io_lib:format(" (Repeated ~p times)",[N]) || N>1], + fmt_value(Value)]). + + +fmt_ts(TS = {_,_,Us}) -> + {{YY,MM,DD},{H,M,S}} = calendar:now_to_universal_time(TS), + io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~.6.0w UTC",[YY,MM,DD,H,M,S,Us]). + +fmt_value(#circ_buf_entry{module = M, + line = L, + function = {F,A}, + pid = Pid, + value = V}) -> + io_lib:format("~p:~p ~p/~p ~p~n~s",[M,L,F,A,Pid,fmt_value(V)]); +fmt_value(Value) -> + io_lib:format("~p",[Value]). diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 55c0548c9b..da4027a763 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -289,12 +289,12 @@ encode(#ssh_msg_kex_dh_gex_reply{ <<?Ebyte(?SSH_MSG_KEX_DH_GEX_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>; encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) -> - <<?Ebyte(?SSH_MSG_KEX_ECDH_INIT), ?Empint(Q_c)>>; + <<?Ebyte(?SSH_MSG_KEX_ECDH_INIT), ?Ebinary(Q_c)>>; encode(#ssh_msg_kex_ecdh_reply{public_host_key = {Key,SigAlg}, q_s = Q_s, h_sig = Sign}) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), EncSign = encode_signature(Key, SigAlg, Sign), - <<?Ebyte(?SSH_MSG_KEX_ECDH_REPLY), ?Ebinary(EncKey), ?Empint(Q_s), ?Ebinary(EncSign)>>; + <<?Ebyte(?SSH_MSG_KEX_ECDH_REPLY), ?Ebinary(EncKey), ?Ebinary(Q_s), ?Ebinary(EncSign)>>; encode(#ssh_msg_ignore{data = Data}) -> <<?Ebyte(?SSH_MSG_IGNORE), ?Estring_utf8(Data)>>; @@ -504,13 +504,13 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), h_sig = decode_signature(Hashsign) }; -decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_MPINT(Q_c,__0)>>) -> +decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_BIN(Q_c,__0)>>) -> #ssh_msg_kex_ecdh_init{ q_c = Q_c }; decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY), - ?DEC_BIN(Key,__1), ?DEC_MPINT(Q_s,__2), ?DEC_BIN(Sig,__3)>>) -> + ?DEC_BIN(Key,__1), ?DEC_BIN(Q_s,__2), ?DEC_BIN(Sig,__3)>>) -> #ssh_msg_kex_ecdh_reply{ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), q_s = Q_s, diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 6e720a47b7..1b2ba5a50b 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -798,13 +798,22 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, Signal, Error0, _}}, State0) -> + Error = + case Error0 of + "" -> Signal; + _ -> Error0 + end, State = reply_all(State0, {error, Error}), {stop, ChannelId, State}; handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State0) -> - State = reply_all(State0, {error, {exit_status, Status}}), + State = + case State0 of + 0 -> State0; + _ -> reply_all(State0, {error, {exit_status, Status}}) + end, {stop, ChannelId, State}. %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 7ee762dcee..278f6a9780 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -125,9 +125,9 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) -> - Report = io_lib:format("Connection closed by peer ~n Error ~p~n", - [Error]), +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, Signal, Error, _}}, State) -> + Report = io_lib:format("Connection closed by peer signal ~p~n Error ~p~n", + [Signal,Error]), error_logger:error_report(Report), {stop, ChannelId, State}; diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 9ec16b420d..c5b0704925 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -36,7 +36,7 @@ default_algorithms/0, default_algorithms/1, algo_classes/0, algo_class/1, algo_two_spec_classes/0, algo_two_spec_class/1, - handle_packet_part/4, + handle_packet_part/5, handle_hello_version/1, key_exchange_init_msg/1, key_init/3, new_keys_message/1, @@ -104,17 +104,14 @@ algo_two_spec_class(_) -> false. default_algorithms(kex) -> supported_algorithms(kex, [ - %% Under devolpment: - 'curve25519-sha256', - '[email protected]', - 'curve448-sha512', %% Gone in OpenSSH 7.3.p1: 'diffie-hellman-group1-sha1' ]); default_algorithms(cipher) -> supported_algorithms(cipher, same(['AEAD_AES_128_GCM', - 'AEAD_AES_256_GCM'])); + 'AEAD_AES_256_GCM' + ])); default_algorithms(mac) -> supported_algorithms(mac, same(['AEAD_AES_128_GCM', 'AEAD_AES_256_GCM'])); @@ -128,18 +125,18 @@ supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()]. supported_algorithms(kex) -> select_crypto_supported( [ - {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, - {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, - {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, - %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves - %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 - {'curve25519-sha256', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, - {'[email protected]', [{public_keys,eddh}, {curves,x25519}, {hashs,sha256}]}, - {'curve448-sha512', [{public_keys,eddh}, {curves,x448}, {hashs,sha512}]}, + {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {curves,secp384r1}, {hashs,sha384}]}, + {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {curves,secp521r1}, {hashs,sha512}]}, + {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {curves,secp256r1}, {hashs,sha256}]}, {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1 {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1 + %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves + %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 + {'curve25519-sha256', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]}, + {'[email protected]', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]}, + {'curve448-sha512', [{public_keys,ecdh}, {curves,x448}, {hashs,sha512}]}, {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} @@ -147,9 +144,9 @@ supported_algorithms(kex) -> supported_algorithms(public_key) -> select_crypto_supported( [ - {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, - {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, - {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {curves,secp384r1}]}, + {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {curves,secp521r1}]}, + {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]}, {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]}, {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]}, @@ -160,6 +157,7 @@ supported_algorithms(cipher) -> same( select_crypto_supported( [ + {'[email protected]', [{ciphers,chacha20}, {macs,poly1305}]}, {'[email protected]', [{ciphers,{aes_gcm,256}}]}, {'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, @@ -174,9 +172,9 @@ supported_algorithms(cipher) -> supported_algorithms(mac) -> same( select_crypto_supported( - [{'hmac-sha2-256', [{hashs,sha256}]}, - {'hmac-sha2-512', [{hashs,sha512}]}, - {'hmac-sha1', [{hashs,sha}]}, + [{'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]}, + {'hmac-sha2-512', [{macs,hmac}, {hashs,sha512}]}, + {'hmac-sha1', [{macs,hmac}, {hashs,sha}]}, {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]} ] @@ -982,13 +980,14 @@ select_algorithm(Role, Client, Server, Opts) -> %%% 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}. +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'=C, _) -> {C, C}; +aead_gcm_simultan('AEAD_AES_256_GCM'=C, _) -> {C, C}; +aead_gcm_simultan(_, 'AEAD_AES_128_GCM'=C) -> {C, C}; +aead_gcm_simultan(_, 'AEAD_AES_256_GCM'=C) -> {C, C}; +aead_gcm_simultan('[email protected]'=C, _)-> {C, C}; +aead_gcm_simultan(Cipher, Mac) -> {Cipher,Mac}. select_encrypt_decrypt(client, Client, Server) -> @@ -1136,7 +1135,7 @@ pack(PlainText, encrypt = CryptoAlg} = Ssh0, PacketLenDeviationForTests) when is_binary(PlainText) -> {Ssh1, CompressedPlainText} = compress(Ssh0, PlainText), - {EcryptedPacket, MAC, Ssh3} = + {FinalPacket, Ssh3} = case pkt_type(CryptoAlg) of common -> PaddingLen = padding_length(4+1+size(CompressedPlainText), Ssh0), @@ -1145,16 +1144,15 @@ pack(PlainText, PlainPacketData = <<?UINT32(PlainPacketLen),?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>, {Ssh2, EcryptedPacket0} = encrypt(Ssh1, PlainPacketData), MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPacketData), - {EcryptedPacket0, MAC0, Ssh2}; + {<<EcryptedPacket0/binary,MAC0/binary>>, 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} + {Ssh2, {EcryptedPacket0,MAC0}} = encrypt(Ssh1, <<?UINT32(PlainPacketLen),PlainPacketData/binary>>), + {<<EcryptedPacket0/binary,MAC0/binary>>, Ssh2} end, - FinalPacket = [EcryptedPacket, MAC], Ssh = Ssh3#ssh{send_sequence = (SeqNum+1) band 16#ffffffff}, {FinalPacket, Ssh}. @@ -1174,31 +1172,31 @@ padding_length(Size, #ssh{encrypt_block_size = BlockSize, -handle_packet_part(<<>>, Encrypted0, undefined, #ssh{decrypt = CryptoAlg} = Ssh0) -> +handle_packet_part(<<>>, Encrypted0, AEAD0, 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}; + {get_more, <<>>, Encrypted0, AEAD0, undefined, Ssh0}; - {ok, PacketLen, _, _, _} when PacketLen > ?SSH_MAX_PACKET_SIZE -> + {ok, PacketLen, _, _, _, _} when PacketLen > ?SSH_MAX_PACKET_SIZE -> %% far too long message than expected {error, {exceeds_max_size,PacketLen}}; - {ok, PacketLen, Decrypted, Encrypted1, + {ok, PacketLen, Decrypted, Encrypted1, AEAD, #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) + handle_packet_part(Decrypted, Encrypted1, AEAD, TotalNeeded, Ssh1) end; -handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0) +handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0) when (size(DecryptedPfx)+size(EncryptedBuffer)) < TotalNeeded -> %% need more bytes to finalize the packet - {get_more, DecryptedPfx, EncryptedBuffer, TotalNeeded, Ssh0}; + {get_more, DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0}; -handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, +handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, #ssh{recv_mac_size = MacSize, decrypt = CryptoAlg} = Ssh0) -> %% enough bytes to decode the packet. @@ -1216,8 +1214,7 @@ handle_packet_part(DecryptedPfx, EncryptedBuffer, TotalNeeded, {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh} end; aead -> - PacketLenBin = DecryptedPfx, - case decrypt(Ssh0, {PacketLenBin,EncryptedSfx,Mac}) of + case decrypt(Ssh0, {AEAD,EncryptedSfx,Mac}) of {Ssh1, error} -> {bad_mac, Ssh1}; {Ssh1, DecryptedSfx} -> @@ -1234,21 +1231,29 @@ get_length(common, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) <<EncBlock:BlockSize/binary, EncryptedRest/binary>> = EncryptedBuffer, {Ssh, <<?UINT32(PacketLen),_/binary>> = Decrypted} = decrypt(Ssh0, EncBlock), - {ok, PacketLen, Decrypted, EncryptedRest, Ssh}; + {ok, PacketLen, Decrypted, EncryptedRest, <<>>, Ssh}; false -> get_more end; + get_length(aead, EncryptedBuffer, Ssh) -> - case size(EncryptedBuffer) >= 4 of - true -> + case {size(EncryptedBuffer) >= 4, Ssh#ssh.decrypt} of + {true, '[email protected]'} -> + <<EncryptedLen:4/binary, EncryptedRest/binary>> = EncryptedBuffer, + {Ssh1, PacketLenBin} = decrypt(Ssh, {length,EncryptedLen}), + <<?UINT32(PacketLen)>> = PacketLenBin, + {ok, PacketLen, PacketLenBin, EncryptedRest, EncryptedLen, Ssh1}; + {true, _} -> <<?UINT32(PacketLen), EncryptedRest/binary>> = EncryptedBuffer, - {ok, PacketLen, <<?UINT32(PacketLen)>>, EncryptedRest, Ssh}; - false -> + {ok, PacketLen, <<?UINT32(PacketLen)>>, EncryptedRest, <<?UINT32(PacketLen)>>, Ssh}; + {false, _} -> get_more end. + pkt_type('AEAD_AES_128_GCM') -> aead; pkt_type('AEAD_AES_256_GCM') -> aead; +pkt_type('[email protected]') -> aead; pkt_type(_) -> common. payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) -> @@ -1353,11 +1358,32 @@ cipher('aes192-ctr') -> cipher('aes256-ctr') -> #cipher_data{key_bytes = 32, iv_bytes = 16, - block_bytes = 16}. + block_bytes = 16}; + +cipher('[email protected]') -> % FIXME: Verify!! + #cipher_data{key_bytes = 32, + iv_bytes = 12, + block_bytes = 8}. + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; +encrypt_init(#ssh{encrypt = '[email protected]', role = client} = Ssh) -> + %% [email protected] uses two independent crypto streams, one (chacha20) + %% for the length used in stream mode, and the other (chacha20-poly1305) as AEAD for + %% the payload and to MAC the length||payload. + %% See draft-josefsson-ssh-chacha20-poly1305-openssh-00 + <<K2:32/binary,K1:32/binary>> = hash(Ssh, "C", 512), + {ok, Ssh#ssh{encrypt_keys = {K1,K2} + % encrypt_block_size = 16, %default = 8. What to set it to? 64 (openssl chacha.h) + % ctx and iv is setup for each packet + }}; +encrypt_init(#ssh{encrypt = '[email protected]', role = server} = Ssh) -> + <<K2:32/binary,K1:32/binary>> = hash(Ssh, "D", 512), + {ok, Ssh#ssh{encrypt_keys = {K1,K2} + % encrypt_block_size = 16, %default = 8. What to set it to? + }}; encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> IV = hash(Ssh, "A", 12*8), <<K:16/binary>> = hash(Ssh, "C", 128), @@ -1458,18 +1484,40 @@ encrypt_final(Ssh) -> encrypt(#ssh{encrypt = none} = Ssh, Data) -> {Ssh, Data}; +encrypt(#ssh{encrypt = '[email protected]', + encrypt_keys = {K1,K2}, + send_sequence = Seq} = Ssh, + <<LenData:4/binary, PayloadData/binary>>) -> + %% Encrypt length + IV1 = <<0:8/unit:8, Seq:8/unit:8>>, + {_,EncLen} = crypto:stream_encrypt(crypto:stream_init(chacha20, K1, IV1), + LenData), + %% Encrypt payload + IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, + {_,EncPayloadData} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, IV2), + PayloadData), + + %% MAC tag + {_,PolyKey} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>), + <<0:32/unit:8>>), + EncBytes = <<EncLen/binary,EncPayloadData/binary>>, + Ctag = crypto:poly1305(PolyKey, EncBytes), + %% Result + {Ssh, {EncBytes,Ctag}}; 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), + encrypt_ctx = IV0} = Ssh, + <<LenData:4/binary, PayloadData/binary>>) -> + {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}), IV = next_gcm_iv(IV0), - {Ssh#ssh{encrypt_ctx = IV}, Enc}; + {Ssh#ssh{encrypt_ctx = IV}, {<<LenData/binary,Ctext/binary>>,Ctag}}; 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), + encrypt_ctx = IV0} = Ssh, + <<LenData:4/binary, PayloadData/binary>>) -> + {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}), IV = next_gcm_iv(IV0), - {Ssh#ssh{encrypt_ctx = IV}, Enc}; + {Ssh#ssh{encrypt_ctx = IV}, {<<LenData/binary,Ctext/binary>>,Ctag}}; encrypt(#ssh{encrypt = '3des-cbc', encrypt_keys = {K1,K2,K3}, encrypt_ctx = IV0} = Ssh, Data) -> @@ -1502,6 +1550,14 @@ encrypt(#ssh{encrypt = 'aes256-ctr', decrypt_init(#ssh{decrypt = none} = Ssh) -> {ok, Ssh}; +decrypt_init(#ssh{decrypt = '[email protected]', role = client} = Ssh) -> + <<K2:32/binary,K1:32/binary>> = hash(Ssh, "D", 512), + {ok, Ssh#ssh{decrypt_keys = {K1,K2} + }}; +decrypt_init(#ssh{decrypt = '[email protected]', role = server} = Ssh) -> + <<K2:32/binary,K1:32/binary>> = hash(Ssh, "C", 512), + {ok, Ssh#ssh{decrypt_keys = {K1,K2} + }}; decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> IV = hash(Ssh, "B", 12*8), <<K:16/binary>> = hash(Ssh, "D", 128), @@ -1602,6 +1658,31 @@ decrypt_final(Ssh) -> decrypt(Ssh, <<>>) -> {Ssh, <<>>}; +decrypt(#ssh{decrypt = '[email protected]', + decrypt_keys = {K1,_K2}, + recv_sequence = Seq} = Ssh, {length,EncryptedLen}) -> + {_State,PacketLenBin} = + crypto:stream_decrypt(crypto:stream_init(chacha20, K1, <<0:8/unit:8, Seq:8/unit:8>>), + EncryptedLen), + {Ssh, PacketLenBin}; +decrypt(#ssh{decrypt = '[email protected]', + decrypt_keys = {_K1,K2}, + recv_sequence = Seq} = Ssh, {AAD,Ctext,Ctag}) -> + %% The length is already decoded and used to divide the input + %% Check the mac (important that it is timing-safe): + {_,PolyKey} = + crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>), + <<0:32/unit:8>>), + case equal_const_time(Ctag, crypto:poly1305(PolyKey, <<AAD/binary,Ctext/binary>>)) of + true -> + %% MAC is ok, decode + IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, + {_,PlainText} = + crypto:stream_decrypt(crypto:stream_init(chacha20,K2,IV2), Ctext), + {Ssh, PlainText}; + false -> + {Ssh,error} + end; decrypt(#ssh{decrypt = none} = Ssh, Data) -> {Ssh, Data}; decrypt(#ssh{decrypt = 'AEAD_AES_128_GCM', @@ -1744,7 +1825,7 @@ send_mac_init(SSH) -> Key = hash(SSH, "F", KeySize), {ok, SSH#ssh { send_mac_key = Key }} end; - aead -> + _ -> %% Not applicable {ok, SSH} end. @@ -1765,7 +1846,7 @@ recv_mac_init(SSH) -> Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)), {ok, SSH#ssh { recv_mac_key = Key }} end; - aead -> + _ -> %% Not applicable {ok, SSH} end. @@ -1812,6 +1893,7 @@ hash(K, H, Ki, N, HashAlg) -> kex_hash(SSH, Key, HashAlg, Args) -> crypto:hash(HashAlg, kex_plaintext(SSH,Key,Args)). + kex_plaintext(SSH, Key, Args) -> EncodedKey = public_key:ssh_encode(Key, ssh2_pubkey), <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version), @@ -1819,8 +1901,13 @@ kex_plaintext(SSH, Key, Args) -> ?Ebinary(EncodedKey), (kex_alg_dependent(Args))/binary>>. + +kex_alg_dependent({Q_c, Q_s, K}) when is_binary(Q_c), is_binary(Q_s) -> + %% ecdh + <<?Ebinary(Q_c), ?Ebinary(Q_s), ?Empint(K)>>; + kex_alg_dependent({E, F, K}) -> - %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s) + %% diffie-hellman <<?Empint(E), ?Empint(F), ?Empint(K)>>; kex_alg_dependent({-1, NBits, -1, Prime, Gen, E, F, K}) -> @@ -1905,6 +1992,7 @@ mac_key_bytes('hmac-sha2-256')-> 32; mac_key_bytes('hmac-sha2-512')-> 64; mac_key_bytes('AEAD_AES_128_GCM') -> 0; mac_key_bytes('AEAD_AES_256_GCM') -> 0; +mac_key_bytes('[email protected]') -> 0; mac_key_bytes(none) -> 0. mac_digest_size('hmac-sha1') -> 20; @@ -1915,6 +2003,7 @@ 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('[email protected]') -> 16; mac_digest_size(none) -> 0. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1937,11 +2026,13 @@ parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}}, Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}. +generate_key(ecdh = Algorithm, Args) -> + crypto:generate_key(Algorithm, Args); generate_key(Algorithm, Args) -> {Public,Private} = crypto:generate_key(Algorithm, Args), {crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}. - + compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> Shared = crypto:compute_key(Algorithm, OthersPublic, MyPrivate, Args), crypto:bytes_to_integer(Shared). @@ -1978,15 +2069,10 @@ supported_algorithms(Key, BlackList) -> select_crypto_supported(L) -> - Sup = [{ec_curve,crypto_supported_curves()} | crypto:supports()], + Sup = 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); @@ -1996,7 +2082,11 @@ crypto_supported(Conditions, Supported) -> end, Conditions). crypto_name_supported(Tag, CryptoName, Supported) -> - lists:member(CryptoName, proplists:get_value(Tag,Supported,[])). + Vs = case proplists:get_value(Tag,Supported,[]) of + [] when Tag == curves -> crypto:ec_curves(); + L -> L + end, + lists:member(CryptoName, Vs). len_supported(Name, Len) -> try @@ -2027,6 +2117,20 @@ same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Compare two binaries in a timing safe maner. +%%% The time spent in comparing should not be different depending on where in the binaries they differ. +%%% This is to avoid a certain side-channel attac. +equal_const_time(X1, X2) -> equal_const_time(X1, X2, true). + +equal_const_time(<<B1,R1/binary>>, <<B2,R2/binary>>, Truth) -> + equal_const_time(R1, R2, Truth and (B1 == B2)); +equal_const_time(<<>>, <<>>, Truth) -> + Truth; +equal_const_time(_, _, _) -> + false. + +%%%-------- Remove CR, LF and following characters from a line + trim_tail(Str) -> lists:takewhile(fun(C) -> C=/=$\r andalso C=/=$\n diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index e1680c120e..7bb9c2d101 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -734,7 +734,7 @@ decode_ATTR(Vsn, <<?UINT32(Flags), Tail/binary>>) -> {Type,Tail2} = if Vsn =< 3 -> {?SSH_FILEXFER_TYPE_UNKNOWN, Tail}; - Vsn >= 5 -> + true -> <<?BYTE(T), TL/binary>> = Tail, {T, TL} end, |