diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/ssh.erl | 10 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 84 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.hrl | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 27 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 16 | ||||
-rw-r--r-- | lib/ssh/src/ssh_file.erl | 63 | ||||
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 32 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 252 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.hrl | 2 |
9 files changed, 318 insertions, 170 deletions
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 132de71aed..b6ee29efbb 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -117,9 +117,9 @@ channel_info(ConnectionRef, ChannelId, Options) -> ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options). %%-------------------------------------------------------------------- --spec daemon(integer()) -> {ok, pid()}. --spec daemon(integer(), proplists:proplist()) -> {ok, pid()}. --spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()}. +-spec daemon(integer()) -> {ok, pid()} | {error, term()}. +-spec daemon(integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. %% Description: Starts a server listening for SSH connections %% on the given port. @@ -385,6 +385,8 @@ handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{max_sessions, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{max_channels, _} = Opt|Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{negotiation_timeout, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{parallel_login, _} = Opt|Rest], SocketOptions, SshOptions) -> @@ -443,6 +445,8 @@ handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value Opt; handle_ssh_option({max_sessions, Value} = Opt) when is_integer(Value), Value>0 -> Opt; +handle_ssh_option({max_channels, Value} = Opt) when is_integer(Value), Value>0 -> + Opt; handle_ssh_option({negotiation_timeout, Value} = Opt) when is_integer(Value); Value == infinity -> Opt; handle_ssh_option({parallel_login, Value} = Opt) when Value==true ; Value==false -> diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 726f52132f..0c16e19701 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -31,8 +31,7 @@ -export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, - handle_userauth_info_request/3, handle_userauth_info_response/2, - default_public_key_algorithms/0 + handle_userauth_info_request/3, handle_userauth_info_response/2 ]). %%-------------------------------------------------------------------- @@ -42,27 +41,29 @@ publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, opts = Opts} = Ssh]) -> - Hash = sha, %% Maybe option?! KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:user_key(Alg, Opts) of {ok, Key} -> StrAlgo = algorithm_string(Alg), - PubKeyBlob = encode_public_key(Key), - SigData = build_sig_data(SessionId, - User, Service, PubKeyBlob, StrAlgo), - Sig = ssh_transport:sign(SigData, Hash, Key), - SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "publickey", - data = [?TRUE, - ?string(StrAlgo), - ?binary(PubKeyBlob), - ?binary(SigBlob)]}, - Ssh); + case encode_public_key(StrAlgo, Key) of + not_ok -> + not_ok; + PubKeyBlob -> + SigData = build_sig_data(SessionId, + User, Service, PubKeyBlob, StrAlgo), + Sig = ssh_transport:sign(SigData, Hash, Key), + SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "publickey", + data = [?TRUE, + ?string(StrAlgo), + ?binary(PubKeyBlob), + ?binary(SigBlob)]}, + Ssh) + end; _Error -> not_ok end. @@ -121,7 +122,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> Algs = proplists:get_value(public_key, proplists:get_value(preferred_algorithms, Opts, []), - default_public_key_algorithms()), + ssh_transport:default_algorithms(public_key)), Prefs = method_preference(Algs), ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, userauth_preference = Prefs, @@ -355,8 +356,6 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{}, language = "en"}). -default_public_key_algorithms() -> ?PREFERRED_PK_ALGS. - %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -431,10 +430,13 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) -> ?binary(KeyBlob)], list_to_binary(Sig). -algorithm_string('ssh-rsa') -> - "ssh-rsa"; -algorithm_string('ssh-dss') -> - "ssh-dss". +algorithm_string('ssh-rsa') -> "ssh-rsa"; +algorithm_string('ssh-dss') -> "ssh-dss"; +algorithm_string('ecdsa-sha2-nistp256') -> "ecdsa-sha2-nistp256"; +algorithm_string('ecdsa-sha2-nistp384') -> "ecdsa-sha2-nistp384"; +algorithm_string('ecdsa-sha2-nistp521') -> "ecdsa-sha2-nistp521". + + decode_keyboard_interactive_prompts(_NumPrompts, Data) -> ssh_message:decode_keyboard_interactive_prompts(Data, []). @@ -497,11 +499,35 @@ decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, ?UINT32(Len4), Y:Len4/big-signed-integer-unit:8>> , "ssh-dss") -> {ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}}; - +decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary, + ?UINT32(Len1), Id:Len1/binary, %% Id = <<"nistp256">> for example + ?UINT32(Len2), Blob:Len2/binary>>, + Curve) -> + Id = + case Curve of + "ecdsa-sha2-nistp256" -> <<"nistp256">>; + "ecdsa-sha2-nistp384" -> <<"nistp384">>; + "ecdsa-sha2-nistp521" -> <<"nistp521">> + end, + {ok, {#'ECPoint'{point=Blob}, Id}}; decode_public_key_v2(_, _) -> {error, bad_format}. -encode_public_key(#'RSAPrivateKey'{publicExponent = E, modulus = N}) -> +encode_public_key("ssh-rsa", #'RSAPrivateKey'{publicExponent = E, modulus = N}) -> ssh_bits:encode(["ssh-rsa",E,N], [string,mpint,mpint]); -encode_public_key(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) -> - ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]). +encode_public_key("ssh-dss", #'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) -> + ssh_bits:encode(["ssh-dss",P,Q,G,Y], [string,mpint,mpint,mpint,mpint]); +encode_public_key("ecdsa-sha2-"++Curve, #'ECPrivateKey'{parameters = Params, + publicKey = Pub}) -> + Id = ecdsa_id(Params), + if + Id =/= Curve -> + not_ok; + true -> + ssh_bits:encode(["ecdsa-sha2-"++Id, Id, Pub], + [string, string, binary]) + end. + +ecdsa_id({namedCurve,?'secp256r1'}) -> "nistp256"; +ecdsa_id({namedCurve,?'secp384r1'}) -> "nistp384"; +ecdsa_id({namedCurve,?'secp521r1'}) -> "nistp521". diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl index 71f222f6d7..5197a42fa4 100644 --- a/lib/ssh/src/ssh_auth.hrl +++ b/lib/ssh/src/ssh_auth.hrl @@ -24,8 +24,6 @@ -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(PREFERRED_PK_ALGS, ['ssh-rsa','ssh-dss']). - -define(SSH_MSG_USERAUTH_REQUEST, 50). -define(SSH_MSG_USERAUTH_FAILURE, 51). -define(SSH_MSG_USERAUTH_SUCCESS, 52). diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 64d2113125..266c64fd4f 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -935,14 +935,27 @@ encode_ip(Addr) when is_list(Addr) -> end end. -start_channel(Cb, Id, Args, SubSysSup) -> - start_channel(Cb, Id, Args, SubSysSup, undefined). +start_channel(Cb, Id, Args, SubSysSup, Opts) -> + start_channel(Cb, Id, Args, SubSysSup, undefined, Opts). -start_channel(Cb, Id, Args, SubSysSup, Exec) -> +start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> ChildSpec = child_spec(Cb, Id, Args, Exec), ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), + assert_limit_num_channels_not_exceeded(ChannelSup, Opts), ssh_channel_sup:start_child(ChannelSup, ChildSpec). +assert_limit_num_channels_not_exceeded(ChannelSup, Opts) -> + MaxNumChannels = proplists:get_value(max_channels, Opts, infinity), + NumChannels = length([x || {_,_,worker,[ssh_channel]} <- + supervisor:which_children(ChannelSup)]), + if + %% Note that NumChannels is BEFORE starting a new one + NumChannels < MaxNumChannels -> + ok; + true -> + throw(max_num_channels_exceeded) + end. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -998,9 +1011,11 @@ child_spec(Callback, Id, Args, Exec) -> start_cli(#connection{cli_spec = no_cli}, _) -> {error, cli_disabled}; -start_cli(#connection{cli_spec = {CbModule, Args}, exec = Exec, +start_cli(#connection{options = Options, + cli_spec = {CbModule, Args}, + exec = Exec, sub_system_supervisor = SubSysSup}, ChannelId) -> - start_channel(CbModule, ChannelId, Args, SubSysSup, Exec). + start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options). start_subsytem(BinName, #connection{options = Options, sub_system_supervisor = SubSysSup}, @@ -1008,7 +1023,7 @@ start_subsytem(BinName, #connection{options = Options, Name = binary_to_list(BinName), case check_subsystem(Name, Options) of {Callback, Opts} when is_atom(Callback), Callback =/= none -> - start_channel(Callback, ChannelId, Opts, SubSysSup); + start_channel(Callback, ChannelId, Opts, SubSysSup, Options); {Other, _} when Other =/= none -> {error, legacy_option_not_supported} end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 646f787874..7fb86c1108 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -530,7 +530,7 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection", Pid ! ssh_connected, connected_fun(User, Address, Method, Opts), {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + next_packet(State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}})}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Address, Reason, Opts), send_msg(Reply, State), @@ -622,7 +622,7 @@ userauth_keyboard_interactive(#ssh_msg_userauth_info_response{} = Msg, Pid ! ssh_connected, connected_fun(User, Address, "keyboard-interactive", Opts), {next_state, connected, - next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + next_packet(State#state{auth_user = User, ssh_params = Ssh#ssh{authenticated = true}})}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Address, Reason, Opts), send_msg(Reply, State), @@ -1266,9 +1266,9 @@ supported_host_keys(client, _, Options) -> proplists:get_value(preferred_algorithms,Options,[]) ) of undefined -> - ssh_auth:default_public_key_algorithms(); + ssh_transport:default_algorithms(public_key); L -> - L -- (L--ssh_auth:default_public_key_algorithms()) + L -- (L--ssh_transport:default_algorithms(public_key)) end of [] -> @@ -1280,21 +1280,17 @@ supported_host_keys(client, _, Options) -> {stop, {shutdown, Reason}} end; supported_host_keys(server, KeyCb, Options) -> - Algs= [atom_to_list(A) || A <- proplists:get_value(public_key, proplists:get_value(preferred_algorithms,Options,[]), - ssh_auth:default_public_key_algorithms() + ssh_transport:default_algorithms(public_key) ), available_host_key(KeyCb, A, Options) - ], - Algs. - + ]. %% Alg :: atom() available_host_key(KeyCb, Alg, Opts) -> element(1, catch KeyCb:host_key(Alg, Opts)) == ok. - send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) -> Transport:send(Socket, Msg). diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index b98a8a8410..4e6d58cbff 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -52,8 +52,20 @@ host_key(Algorithm, Opts) -> %% so probably we could hardcod Password = ignore, but %% we keep it as an undocumented option for now. Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), - decode(File, Password). - + case decode(File, Password) of + {ok,Key} -> + case {Key,Algorithm} of + {#'RSAPrivateKey'{}, 'ssh-rsa'} -> {ok,Key}; + {#'DSAPrivateKey'{}, 'ssh-dss'} -> {ok,Key}; + {#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}, 'ecdsa-sha2-nistp256'} -> {ok,Key}; + {#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}, 'ecdsa-sha2-nistp384'} -> {ok,Key}; + {#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}, 'ecdsa-sha2-nistp521'} -> {ok,Key}; + _ -> + {error,bad_keytype_in_file} + end; + Other -> + Other + end. is_auth_key(Key, User,Opts) -> case lookup_user_key(Key, User, Opts) of @@ -81,16 +93,15 @@ user_key(Algorithm, Opts) -> %% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -file_base_name('ssh-rsa') -> - "ssh_host_rsa_key"; -file_base_name('ssh-dss') -> - "ssh_host_dsa_key"; -file_base_name(_) -> - "ssh_host_key". +file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; +file_base_name('ssh-dss' ) -> "ssh_host_dsa_key"; +file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; +file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; +file_base_name('ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key"; +file_base_name(_ ) -> "ssh_host_key". decode(File, Password) -> - try - {ok, decode_ssh_file(read_ssh_file(File), Password)} + try {ok, decode_ssh_file(read_ssh_file(File), Password)} catch throw:Reason -> {error, Reason}; @@ -215,20 +226,18 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> Error -> Error end. -identity_key_filename('ssh-dss') -> - "id_dsa"; -identity_key_filename('ssh-rsa') -> - "id_rsa". - -identity_pass_phrase("ssh-dss") -> - dsa_pass_phrase; -identity_pass_phrase('ssh-dss') -> - dsa_pass_phrase; -identity_pass_phrase('ssh-rsa') -> - rsa_pass_phrase; -identity_pass_phrase("ssh-rsa") -> - rsa_pass_phrase. - +identity_key_filename('ssh-dss' ) -> "id_dsa"; +identity_key_filename('ssh-rsa' ) -> "id_rsa"; +identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa"; +identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa"; +identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa". + +identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase; +identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase; +identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase; +identity_pass_phrase(P) when is_atom(P) -> + identity_pass_phrase(atom_to_list(P)). + lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) -> case io:get_line(Fd, '') of eof -> @@ -267,6 +276,12 @@ key_match(#'RSAPublicKey'{}, 'ssh-rsa') -> true; key_match({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; +key_match({#'ECPoint'{},<<"nistp256">>}, 'ecdsa-sha2-nistp256') -> + true; +key_match({#'ECPoint'{},<<"nistp384">>}, 'ecdsa-sha2-nistp384') -> + true; +key_match({#'ECPoint'{},<<"nistp521">>}, 'ecdsa-sha2-nistp521') -> + true; key_match(_, _) -> false. diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index cb1dcb67c5..cfa11903fb 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -564,9 +564,11 @@ decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) -> decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> Signature. + decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) -> decode_host_key(Alg, Rest). + decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/big-signed-integer-unit:8, ?UINT32(Len1), N:Len1/big-signed-integer-unit:8>>) -> #'RSAPublicKey'{publicExponent = E, @@ -579,19 +581,43 @@ decode_host_key(<<"ssh-dss">>, ?UINT32(Len3), Y:Len3/big-signed-integer-unit:8>>) -> {Y, #'Dss-Parms'{p = P, q = Q, - g = G}}. + g = G}}; + +decode_host_key(<<"ecdsa-sha2-",Id/binary>>, + <<?UINT32(Len0), Id:Len0/binary, %% Id = <<"nistp256">> for example + ?UINT32(Len1), Blob:Len1/binary>>) -> + {#'ECPoint'{point=Blob}, Id}. + encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); encode_host_key({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) -> ssh_bits:encode(["ssh-dss", P, Q, G, Y], [string, mpint, mpint, mpint, mpint]); +encode_host_key({#'ECPoint'{point = Q}, Id}) -> + ssh_bits:encode([<<"ecdsa-sha2-",Id/binary>>,Id,Q], [binary,binary,binary]); + encode_host_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); encode_host_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> ssh_bits:encode(["ssh-dss", P, Q, G, Y], - [string, mpint, mpint, mpint, mpint]). + [string, mpint, mpint, mpint, mpint]); +encode_host_key(#'ECPrivateKey'{parameters = Params, %{namedCurve,{1,2,840,10045,3,1,7}}, + publicKey = Pub}) -> + Id = ecdsa_id(Params), + ssh_bits:encode(["ecdsa-sha2-"++Id, Id, Pub], + [string, string, binary]). + + encode_sign(#'RSAPrivateKey'{}, Signature) -> ssh_bits:encode(["ssh-rsa", Signature],[string, binary]); encode_sign(#'DSAPrivateKey'{}, Signature) -> - ssh_bits:encode(["ssh-dss", Signature],[string, binary]). + ssh_bits:encode(["ssh-dss", Signature],[string, binary]); +encode_sign(#'ECPrivateKey'{parameters = Params}, Signature) -> + Id = "ecdsa-sha2-" ++ ecdsa_id(Params), + ssh_bits:encode([Id, Signature],[string, binary]). + + +ecdsa_id({namedCurve,?'secp256r1'}) -> "nistp256"; +ecdsa_id({namedCurve,?'secp384r1'}) -> "nistp384"; +ecdsa_id({namedCurve,?'secp521r1'}) -> "nistp521". diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 2b6f0a3cdc..a6438e69d4 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -65,9 +65,8 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()]. algo_classes() -> [kex, public_key, cipher, mac, compression]. -default_algorithms(compression) -> - %% Do not announce '[email protected]' because there seem to be problems - supported_algorithms(compression, same(['[email protected]'])); +default_algorithms(kex) -> + supported_algorithms(kex, []); %% Just to have a call to supported_algorithms/2 default_algorithms(Alg) -> supported_algorithms(Alg). @@ -79,18 +78,27 @@ supported_algorithms(kex) -> [ {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]}, {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]}, + {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, + {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, + {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]}, - {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]}, - {'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]}, - {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} + {'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]} ]); supported_algorithms(public_key) -> - ssh_auth:default_public_key_algorithms(); + select_crypto_supported( + [{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]}, + {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]}, + {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]}, + {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]}, + {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} + ]); + supported_algorithms(cipher) -> same( select_crypto_supported( - [{'aes128-ctr', [{ciphers,aes_ctr}]}, + [{'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, + {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, + {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, {'aes128-cbc', [{ciphers,aes_cbc128}]}, {'3des-cbc', [{ciphers,des3_cbc}]} ] @@ -98,14 +106,16 @@ supported_algorithms(cipher) -> supported_algorithms(mac) -> same( select_crypto_supported( - [{'hmac-sha2-512', [{hashs,sha512}]}, - {'hmac-sha2-256', [{hashs,sha256}]}, + [{'hmac-sha2-256', [{hashs,sha256}]}, + {'hmac-sha2-512', [{hashs,sha512}]}, {'hmac-sha1', [{hashs,sha}]} ] )); supported_algorithms(compression) -> - same(['none','zlib','[email protected]']). - + same(['none', + '[email protected]', + 'zlib' + ]). supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) -> [{client2server,As1},{server2client,As2}] = supported_algorithms(Key), @@ -124,10 +134,25 @@ crypto_supported_curves() -> end. crypto_supported(Conditions, Supported) -> - lists:all( fun({Tag,CryptoName}) -> - lists:member(CryptoName, proplists:get_value(Tag,Supported,[])) + lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) -> + crypto_name_supported(Tag,CryptoName,Supported); + ({Tag,{Name=aes_ctr,Len}}) when is_integer(Len) -> + crypto_name_supported(Tag,Name,Supported) andalso + ctr_len_supported(Name,Len) end, Conditions). +crypto_name_supported(Tag, CryptoName, Supported) -> + lists:member(CryptoName, proplists:get_value(Tag,Supported,[])). + +ctr_len_supported(Name, Len) -> + try + crypto:stream_encrypt(crypto:stream_init(Name, <<0:Len>>, <<0:128>>), <<"">>) + of + {_,X} -> is_binary(X) + catch + _:_ -> false + end. + same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. @@ -319,11 +344,12 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ; {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}; -key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group-exchange-sha1' ; - Kex == 'diffie-hellman-group-exchange-sha256' -> - Min = ?DEFAULT_DH_GROUP_MIN, - NBits = ?DEFAULT_DH_GROUP_NBITS, - Max = ?DEFAULT_DH_GROUP_MAX, +key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ; + Kex == 'diffie-hellman-group-exchange-sha256' -> + {Min,NBits,Max} = + proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN, + ?DEFAULT_DH_GROUP_NBITS, + ?DEFAULT_DH_GROUP_MAX}), {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min, n = NBits, @@ -623,33 +649,40 @@ get_host_key(SSH) -> #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH, case Mod:host_key(ALG#alg.hkey, Opts) of - {ok, #'RSAPrivateKey'{} = Key} -> - Key; - {ok, #'DSAPrivateKey'{} = Key} -> - Key; + {ok, #'RSAPrivateKey'{} = Key} -> Key; + {ok, #'DSAPrivateKey'{} = Key} -> Key; + {ok, #'ECPrivateKey'{} = Key} -> Key; Result -> exit({error, {Result, unsupported_key_type}}) end. -sign_host_key(_Ssh, #'RSAPrivateKey'{} = Private, H) -> - Hash = sha, - _Signature = sign(H, Hash, Private); -sign_host_key(_Ssh, #'DSAPrivateKey'{} = Private, H) -> - Hash = sha, - _RawSignature = sign(H, Hash, Private). +sign_host_key(_Ssh, PrivateKey, H) -> + sign(H, sign_host_key_sha(PrivateKey), PrivateKey). + +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}) -> sha256; +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}) -> sha384; +sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}) -> sha512; +sign_host_key_sha(#'RSAPrivateKey'{}) -> sha; +sign_host_key_sha(#'DSAPrivateKey'{}) -> sha. + verify_host_key(SSH, PublicKey, Digest, Signature) -> - case verify(Digest, sha, Signature, PublicKey) of + case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of false -> {error, bad_signature}; true -> known_host_key(SSH, PublicKey, public_algo(PublicKey)) end. -public_algo(#'RSAPublicKey'{}) -> - 'ssh-rsa'; -public_algo({_, #'Dss-Parms'{}}) -> - 'ssh-dss'. +host_key_sha(#'RSAPublicKey'{}) -> sha; +host_key_sha({_, #'Dss-Parms'{}}) -> sha; +host_key_sha({#'ECPoint'{},Id}) -> sha(list_to_atom(binary_to_list(Id))). + + +public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; +public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; +public_algo({#'ECPoint'{},Id}) -> list_to_atom("ecdsa-sha2-" ++ binary_to_list(Id)). + accepted_host(Ssh, PeerName, Opts) -> case proplists:get_value(silently_accept_hosts, Opts, false) of @@ -889,6 +922,10 @@ sign(SigData, Hash, #'DSAPrivateKey'{} = Key) -> DerSignature = public_key:sign(SigData, Hash, Key), #'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature), <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>; +sign(SigData, Hash, Key = #'ECPrivateKey'{}) -> + DerEncodedSign = public_key:sign(SigData, Hash, Key), + #'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign), + ssh_bits:encode([R,S], [mpint,mpint]); sign(SigData, Hash, Key) -> public_key:sign(SigData, Hash, Key). @@ -896,55 +933,23 @@ verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) -> <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = Sig, Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}), public_key:verify(PlainText, Hash, Signature, Key); +verify(PlainText, Hash, Sig, {ECPoint=#'ECPoint'{}, Param}) -> + C = case Param of + <<"nistp256">> -> {namedCurve, ?'secp256r1'}; + <<"nistp384">> -> {namedCurve, ?'secp384r1'}; + <<"nistp521">> -> {namedCurve, ?'secp521r1'} + end, + <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8, + ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> = Sig, + Sval = #'ECDSA-Sig-Value'{r=R, s=S}, + DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval), + public_key:verify(PlainText, Hash, DerEncodedSig, {ECPoint,C}); verify(PlainText, Hash, Sig, Key) -> public_key:verify(PlainText, Hash, Sig, Key). -%% public key algorithms -%% -%% ssh-dss REQUIRED sign Raw DSS Key -%% ssh-rsa RECOMMENDED sign Raw RSA Key -%% x509v3-sign-rsa OPTIONAL sign X.509 certificates (RSA key) -%% x509v3-sign-dss OPTIONAL sign X.509 certificates (DSS key) -%% spki-sign-rsa OPTIONAL sign SPKI certificates (RSA key) -%% spki-sign-dss OPTIONAL sign SPKI certificates (DSS key) -%% pgp-sign-rsa OPTIONAL sign OpenPGP certificates (RSA key) -%% pgp-sign-dss OPTIONAL sign OpenPGP certificates (DSS key) -%% - -%% key exchange -%% -%% diffie-hellman-group1-sha1 REQUIRED -%% diffie-hellman-group14-sha1 REQUIRED -%% -%% - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Encryption -%% -%% chiphers %% -%% 3des-cbc REQUIRED -%% three-key 3DES in CBC mode -%% blowfish-cbc OPTIONAL Blowfish in CBC mode -%% twofish256-cbc OPTIONAL Twofish in CBC mode, -%% with 256-bit key -%% twofish-cbc OPTIONAL alias for "twofish256-cbc" (this -%% is being retained for -%% historical reasons) -%% twofish192-cbc OPTIONAL Twofish with 192-bit key -%% twofish128-cbc OPTIONAL Twofish with 128-bit key -%% aes256-cbc OPTIONAL AES in CBC mode, -%% with 256-bit key -%% aes192-cbc OPTIONAL AES with 192-bit key -%% aes128-cbc RECOMMENDED AES with 128-bit key -%% serpent256-cbc OPTIONAL Serpent in CBC mode, with -%% 256-bit key -%% serpent192-cbc OPTIONAL Serpent with 192-bit key -%% serpent128-cbc OPTIONAL Serpent with 128-bit key -%% arcfour OPTIONAL the ARCFOUR stream cipher -%% idea-cbc OPTIONAL IDEA in CBC mode -%% cast128-cbc OPTIONAL CAST-128 in CBC mode -%% none OPTIONAL no encryption; NOT RECOMMENDED +%% Encryption %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -975,18 +980,46 @@ encrypt_init(#ssh{encrypt = 'aes128-cbc', role = server} = Ssh) -> encrypt_block_size = 16, encrypt_ctx = IV}}; encrypt_init(#ssh{encrypt = 'aes128-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "A", 128), + IV = hash(Ssh, "A", 128), <<K:16/binary>> = hash(Ssh, "C", 128), State = crypto:stream_init(aes_ctr, K, IV), {ok, Ssh#ssh{encrypt_keys = K, encrypt_block_size = 16, encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes192-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:24/binary>> = hash(Ssh, "C", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, + encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes256-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:32/binary>> = hash(Ssh, "C", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, + encrypt_ctx = State}}; encrypt_init(#ssh{encrypt = 'aes128-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "B", 128), + IV = hash(Ssh, "B", 128), <<K:16/binary>> = hash(Ssh, "D", 128), State = crypto:stream_init(aes_ctr, K, IV), {ok, Ssh#ssh{encrypt_keys = K, encrypt_block_size = 16, + encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes192-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:24/binary>> = hash(Ssh, "D", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, + encrypt_ctx = State}}; +encrypt_init(#ssh{encrypt = 'aes256-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:32/binary>> = hash(Ssh, "D", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{encrypt_keys = K, + encrypt_block_size = 16, encrypt_ctx = State}}. encrypt_final(Ssh) -> @@ -1013,6 +1046,14 @@ encrypt(#ssh{encrypt = 'aes128-cbc', encrypt(#ssh{encrypt = 'aes128-ctr', encrypt_ctx = State0} = Ssh, Data) -> {State, Enc} = crypto:stream_encrypt(State0,Data), + {Ssh#ssh{encrypt_ctx = State}, Enc}; +encrypt(#ssh{encrypt = 'aes192-ctr', + encrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_encrypt(State0,Data), + {Ssh#ssh{encrypt_ctx = State}, Enc}; +encrypt(#ssh{encrypt = 'aes256-ctr', + encrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_encrypt(State0,Data), {Ssh#ssh{encrypt_ctx = State}, Enc}. @@ -1053,12 +1094,40 @@ decrypt_init(#ssh{decrypt = 'aes128-ctr', role = client} = Ssh) -> {ok, Ssh#ssh{decrypt_keys = K, decrypt_block_size = 16, decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes192-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:24/binary>> = hash(Ssh, "D", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, + decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes256-ctr', role = client} = Ssh) -> + IV = hash(Ssh, "B", 128), + <<K:32/binary>> = hash(Ssh, "D", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, + decrypt_ctx = State}}; decrypt_init(#ssh{decrypt = 'aes128-ctr', role = server} = Ssh) -> IV = hash(Ssh, "A", 128), <<K:16/binary>> = hash(Ssh, "C", 128), State = crypto:stream_init(aes_ctr, K, IV), {ok, Ssh#ssh{decrypt_keys = K, decrypt_block_size = 16, + decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes192-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:24/binary>> = hash(Ssh, "C", 192), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, + decrypt_ctx = State}}; +decrypt_init(#ssh{decrypt = 'aes256-ctr', role = server} = Ssh) -> + IV = hash(Ssh, "A", 128), + <<K:32/binary>> = hash(Ssh, "C", 256), + State = crypto:stream_init(aes_ctr, K, IV), + {ok, Ssh#ssh{decrypt_keys = K, + decrypt_block_size = 16, decrypt_ctx = State}}. @@ -1084,6 +1153,14 @@ decrypt(#ssh{decrypt = 'aes128-cbc', decrypt_keys = Key, decrypt(#ssh{decrypt = 'aes128-ctr', decrypt_ctx = State0} = Ssh, Data) -> {State, Enc} = crypto:stream_decrypt(State0,Data), + {Ssh#ssh{decrypt_ctx = State}, Enc}; +decrypt(#ssh{decrypt = 'aes192-ctr', + decrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_decrypt(State0,Data), + {Ssh#ssh{decrypt_ctx = State}, Enc}; +decrypt(#ssh{decrypt = 'aes256-ctr', + decrypt_ctx = State0} = Ssh, Data) -> + {State, Enc} = crypto:stream_decrypt(State0,Data), {Ssh#ssh{decrypt_ctx = State}, Enc}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1168,17 +1245,8 @@ decompress(#ssh{decompress = '[email protected]', decompress_ctx = Context, authe {Ssh, list_to_binary(Decompressed)}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% MAC calculation %% -%% hmac-sha1 REQUIRED HMAC-SHA1 (digest length = key -%% length = 20) -%% hmac-sha1-96 RECOMMENDED first 96 bits of HMAC-SHA1 (digest -%% length = 12, key length = 20) -%% hmac-md5 OPTIONAL HMAC-MD5 (digest length = key -%% length = 16) -%% hmac-md5-96 OPTIONAL first 96 bits of HMAC-MD5 (digest -%% length = 12, key length = 16) -%% none OPTIONAL no MAC; NOT RECOMMENDED +%% MAC calculation %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1350,7 +1418,7 @@ dh_gex_group(Min, N, Max, undefined) -> dh_gex_group(Min, N, Max, dh_gex_default_groups()); dh_gex_group(Min, N, Max, Groups) -> %% First try to find an exact match. If not an exact match, select the largest possible. - {_,Group} = + {_Size,Group} = lists:foldl( fun(_, {I,G}) when I==N -> %% If we have an exact match already: use that one diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl index 96ab1bb668..17a0daebe3 100644 --- a/lib/ssh/src/ssh_transport.hrl +++ b/lib/ssh/src/ssh_transport.hrl @@ -33,7 +33,7 @@ -define(MAX_NUM_ALGORITHMS, 200). -define(DEFAULT_DH_GROUP_MIN, 1024). --define(DEFAULT_DH_GROUP_NBITS, 6144). +-define(DEFAULT_DH_GROUP_NBITS, 2048). -define(DEFAULT_DH_GROUP_MAX, 8192). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |