aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh/src')
-rw-r--r--lib/ssh/src/ssh.erl6
-rw-r--r--lib/ssh/src/ssh.hrl2
-rw-r--r--lib/ssh/src/ssh_auth.erl5
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl121
-rw-r--r--lib/ssh/src/ssh_transport.erl57
5 files changed, 122 insertions, 69 deletions
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 1a5d48baca..032d87bdad 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -188,6 +188,7 @@ daemon(Port) ->
daemon(Socket, UserOptions) when is_port(Socket) ->
try
#{} = Options = ssh_options:handle_options(server, UserOptions),
+
case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
ok ->
{ok, {IP,Port}} = inet:sockname(Socket),
@@ -461,6 +462,9 @@ open_listen_socket(_Host0, Port0, Options0) ->
%%%----------------------------------------------------------------
finalize_start(Host, Port, Profile, Options0, F) ->
try
+ %% throws error:Error if no usable hostkey is found
+ ssh_connection_handler:available_hkey_algorithms(server, Options0),
+
sshd_sup:start_child(Host, Port, Profile, Options0)
of
{error, {already_started, _}} ->
@@ -470,6 +474,8 @@ finalize_start(Host, Port, Profile, Options0, F) ->
Result = {ok,_} ->
F(Options0, Result)
catch
+ error:{shutdown,Err} ->
+ {error,Err};
exit:{noproc, _} ->
{error, ssh_not_started}
end.
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index d6d412db43..3dee1c5521 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -63,8 +63,8 @@
-define(uint16(X), << ?UINT16(X) >> ).
-define(uint32(X), << ?UINT32(X) >> ).
-define(uint64(X), << ?UINT64(X) >> ).
--define(string(X), << ?STRING(list_to_binary(X)) >> ).
-define(string_utf8(X), << ?STRING(unicode:characters_to_binary(X)) >> ).
+-define(string(X), ?string_utf8(X)).
-define(binary(X), << ?STRING(X) >>).
%% Cipher details
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index ac64a7bf14..894877f8bf 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -145,14 +145,17 @@ get_public_key(SigAlg, #ssh{opts = Opts}) ->
case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
{ok, PrivKey} ->
try
+ %% Check the key - the KeyCb may be a buggy plugin
+ true = ssh_transport:valid_key_sha_alg(PrivKey, KeyAlg),
Key = ssh_transport:extract_public_key(PrivKey),
public_key:ssh_encode(Key, ssh2_pubkey)
of
PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}}
catch
_:_ ->
- not_ok
+ not_ok
end;
+
_Error ->
not_ok
end.
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 4158a52a27..0ca960ef96 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -46,6 +46,7 @@
%%% Internal application API
-export([start_connection/4,
+ available_hkey_algorithms/2,
open_channel/6,
request/6, request/7,
reply_request/3,
@@ -324,23 +325,32 @@ renegotiate_data(ConnectionHandler) ->
%% Internal process state
%%====================================================================
-record(data, {
- starter :: pid(),
+ starter :: pid()
+ | undefined,
auth_user :: string()
| undefined,
connection_state :: #connection{},
- latest_channel_id = 0 :: non_neg_integer(),
+ latest_channel_id = 0 :: non_neg_integer()
+ | undefined,
idle_timer_ref :: undefined
| infinity
| reference(),
idle_timer_value = infinity :: infinity
| pos_integer(),
- transport_protocol :: atom(), % ex: tcp
- transport_cb :: atom(), % ex: gen_tcp
- transport_close_tag :: atom(), % ex: tcp_closed
- ssh_params :: #ssh{},
- socket :: inet:socket(),
- decrypted_data_buffer = <<>> :: binary(),
- encrypted_data_buffer = <<>> :: binary(),
+ transport_protocol :: atom()
+ | undefined, % ex: tcp
+ transport_cb :: atom()
+ | undefined, % ex: gen_tcp
+ transport_close_tag :: atom()
+ | undefined, % ex: tcp_closed
+ ssh_params :: #ssh{}
+ | undefined,
+ socket :: inet:socket()
+ | undefined,
+ decrypted_data_buffer = <<>> :: binary()
+ | undefined,
+ encrypted_data_buffer = <<>> :: binary()
+ | undefined,
undecrypted_packet_length :: undefined | non_neg_integer(),
key_exchange_init_msg :: #ssh_msg_kexinit{}
| undefined,
@@ -369,16 +379,17 @@ init_connection_handler(Role, Socket, Opts) ->
StartState,
D);
- {stop, enotconn} ->
- %% Handles the abnormal sequence:
- %% SYN->
- %% <-SYNACK
- %% ACK->
- %% RST->
- exit({shutdown, "TCP connection to server was prematurely closed by the client"});
-
- {stop, OtherError} ->
- exit({shutdown, {init,OtherError}})
+ {stop, Error} ->
+ Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
+ C = #connection{system_supervisor = proplists:get_value(system_sup, Sups),
+ sub_system_supervisor = proplists:get_value(subsystem_sup, Sups),
+ connection_supervisor = proplists:get_value(connection_sup, Sups)
+ },
+ gen_statem:enter_loop(?MODULE,
+ [],
+ {init_error,Error},
+ #data{connection_state=C,
+ socket=Socket})
end.
@@ -432,13 +443,12 @@ init_ssh_record(Role, Socket, Opts) ->
init_ssh_record(Role, Socket, PeerAddr, Opts).
init_ssh_record(Role, _Socket, PeerAddr, Opts) ->
- KeyCb = ?GET_OPT(key_cb, Opts),
AuthMethods = ?GET_OPT(auth_methods, Opts),
S0 = #ssh{role = Role,
- key_cb = KeyCb,
+ key_cb = ?GET_OPT(key_cb, Opts),
opts = Opts,
userauth_supported_methods = AuthMethods,
- available_host_keys = supported_host_keys(Role, KeyCb, Opts),
+ available_host_keys = available_hkey_algorithms(Role, Opts),
random_length_padding = ?GET_OPT(max_random_length_padding, Opts)
},
@@ -531,6 +541,21 @@ renegotiation(_) -> false.
callback_mode() ->
handle_event_function.
+
+handle_event(_, _Event, {init_error,Error}, _) ->
+ case Error of
+ enotconn ->
+ %% Handles the abnormal sequence:
+ %% SYN->
+ %% <-SYNACK
+ %% ACK->
+ %% RST->
+ {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}};
+
+ OtherError ->
+ {stop, {shutdown,{init,OtherError}}}
+ end;
+
%%% ######## {hello, client|server} ####
%% The very first event that is sent when the we are set as controlling process of Socket
handle_event(_, socket_control, {hello,_}, D) ->
@@ -1544,44 +1569,42 @@ peer_role(client) -> server;
peer_role(server) -> client.
%%--------------------------------------------------------------------
-supported_host_keys(client, _, Options) ->
- try
- find_sup_hkeys(Options)
- of
- [] ->
+available_hkey_algorithms(Role, Options) ->
+ KeyCb = ?GET_OPT(key_cb, Options),
+ case [A || A <- available_hkey_algos(Options),
+ (Role==client) orelse available_host_key(KeyCb, A, Options)
+ ] of
+
+ [] when Role==client ->
error({shutdown, "No public key algs"});
- Algs ->
- [atom_to_list(A) || A<-Algs]
- catch
- exit:Reason ->
- error({shutdown, Reason})
- end;
-supported_host_keys(server, KeyCb, Options) ->
- [atom_to_list(A) || A <- find_sup_hkeys(Options),
- available_host_key(KeyCb, A, Options)
- ].
+ [] when Role==server ->
+ error({shutdown, "No host key available"});
-find_sup_hkeys(Options) ->
- case proplists:get_value(public_key,
- ?GET_OPT(preferred_algorithms,Options)
- )
- of
- undefined ->
- ssh_transport:default_algorithms(public_key);
- L ->
- NonSupported = L--ssh_transport:supported_algorithms(public_key),
- L -- NonSupported
+ Algs ->
+ [atom_to_list(A) || A<-Algs]
end.
+available_hkey_algos(Options) ->
+ SupAlgos = ssh_transport:supported_algorithms(public_key),
+ HKeys = proplists:get_value(public_key,
+ ?GET_OPT(preferred_algorithms,Options)
+ ),
+ NonSupported = HKeys -- SupAlgos,
+ AvailableAndSupported = HKeys -- NonSupported,
+ AvailableAndSupported.
+
%% Alg :: atom()
available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) ->
UserOpts = ?GET_OPT(user_options, Opts),
case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
- {ok,_} -> true;
- _ -> false
+ {ok,Key} ->
+ %% Check the key - the KeyCb may be a buggy plugin
+ ssh_transport:valid_key_sha_alg(Key, Alg);
+ _ ->
+ false
end.
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index e92c727559..90a94a7e86 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -795,8 +795,14 @@ get_host_key(SSH, SignAlg) ->
#ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH,
UserOpts = ?GET_OPT(user_options, Opts),
case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
- {ok, PrivHostKey} -> PrivHostKey;
- Result -> exit({error, {Result, unsupported_key_type}})
+ {ok, PrivHostKey} ->
+ %% Check the key - the KeyCb may be a buggy plugin
+ case valid_key_sha_alg(PrivHostKey, SignAlg) of
+ true -> PrivHostKey;
+ false -> exit({error, bad_hostkey})
+ end;
+ Result ->
+ exit({error, {Result, unsupported_key_type}})
end.
extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
@@ -805,7 +811,15 @@ extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->
{Y, #'Dss-Parms'{p=P, q=Q, g=G}};
extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
publicKey = Q}) ->
- {#'ECPoint'{point=Q}, {namedCurve,OID}}.
+ {#'ECPoint'{point=Q}, {namedCurve,OID}};
+extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) ->
+ case {Alg, crypto:privkey_to_pubkey(Alg, M)} of
+ {rsa, [E,N]} ->
+ #'RSAPublicKey'{modulus = N, publicExponent = E};
+ {dss, [P,Q,G,Y]} ->
+ {Y, #'Dss-Parms'{p=P, q=Q, g=G}}
+ end.
+
verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) ->
@@ -1255,10 +1269,12 @@ payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
<<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
Payload.
+sign(SigData, HashAlg, #{algorithm:=dss} = Key) ->
+ mk_dss_sig(crypto:sign(dss, HashAlg, SigData, Key));
+sign(SigData, HashAlg, #{algorithm:=SigAlg} = Key) ->
+ crypto:sign(SigAlg, HashAlg, SigData, Key);
sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) ->
- DerSignature = public_key:sign(SigData, HashAlg, 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>>;
+ mk_dss_sig(public_key:sign(SigData, HashAlg, Key));
sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) ->
DerEncodedSign = public_key:sign(SigData, HashAlg, Key),
#'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign),
@@ -1266,6 +1282,12 @@ sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) ->
sign(SigData, HashAlg, Key) ->
public_key:sign(SigData, HashAlg, Key).
+
+mk_dss_sig(DerSignature) ->
+ #'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>>.
+
+
verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) ->
case Sig of
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> ->
@@ -1817,6 +1839,8 @@ kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) ->
%%%----------------------------------------------------------------
+valid_key_sha_alg(#{engine:=_, key_id:=_}, _Alg) -> true; % Engine key
+
valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true;
valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true;
valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true;
@@ -1830,11 +1854,14 @@ valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true;
valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true;
valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true;
-valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg);
-valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg);
+valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg);
+valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg);
valid_key_sha_alg(_, _) -> false.
-
+valid_key_sha_alg_ec(OID, Alg) ->
+ Curve = public_key:oid2ssh_curvename(OID),
+ Alg == list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
+
public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2
public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
@@ -2000,12 +2027,6 @@ same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
trim_tail(Str) ->
- lists:reverse(trim_head(lists:reverse(Str))).
-
-trim_head([$\s|Cs]) -> trim_head(Cs);
-trim_head([$\t|Cs]) -> trim_head(Cs);
-trim_head([$\n|Cs]) -> trim_head(Cs);
-trim_head([$\r|Cs]) -> trim_head(Cs);
-trim_head(Cs) -> Cs.
-
-
+ lists:takewhile(fun(C) ->
+ C=/=$\r andalso C=/=$\n
+ end, Str).