aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src')
-rw-r--r--lib/ssh/src/ssh.hrl31
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl15
-rw-r--r--lib/ssh/src/ssh_dbg.erl116
-rw-r--r--lib/ssh/src/ssh_message.erl8
-rw-r--r--lib/ssh/src/ssh_sftp.erl13
-rw-r--r--lib/ssh/src/ssh_sftpd.erl6
-rw-r--r--lib/ssh/src/ssh_transport.erl234
-rw-r--r--lib/ssh/src/ssh_xfer.erl2
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' |
+ 'curve448-sha512' |
'ecdh-sha2-nistp256' |
'ecdh-sha2-nistp384' |
'ecdh-sha2-nistp521'
@@ -140,7 +143,8 @@
'aes192-ctr' |
'aes256-ctr' |
.
-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',
- '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,