diff options
Diffstat (limited to 'lib/ssh')
75 files changed, 2763 insertions, 692 deletions
diff --git a/lib/ssh/Makefile b/lib/ssh/Makefile index dedc7ac3a6..da0e3e6cd1 100644 --- a/lib/ssh/Makefile +++ b/lib/ssh/Makefile @@ -17,6 +17,7 @@ # # %CopyrightEnd% # +# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk @@ -37,4 +38,6 @@ SPECIAL_TARGETS = # include $(ERL_TOP)/make/otp_subdir.mk +DIA_PLT_APPS=crypto runtime_tools public_key +include $(ERL_TOP)/make/app_targets.mk diff --git a/lib/ssh/doc/src/Makefile b/lib/ssh/doc/src/Makefile index 4e32dd9976..4e6af79a1a 100644 --- a/lib/ssh/doc/src/Makefile +++ b/lib/ssh/doc/src/Makefile @@ -30,11 +30,6 @@ VSN=$(SSH_VSN) APPLICATION=ssh # ---------------------------------------------------- -# Release directory specification -# ---------------------------------------------------- -RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) - -# ---------------------------------------------------- # Target Specs # ---------------------------------------------------- XML_APPLICATION_FILES = ref_man.xml @@ -67,87 +62,9 @@ XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6 IMAGE_FILES = SSH_protocols.png -# ---------------------------------------------------- - -HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ - $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) - -INFO_FILE = ../../info -EXTRA_FILES = \ - $(DEFAULT_GIF_FILES) \ - $(DEFAULT_HTML_FILES) \ - $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \ - $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \ - $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) - - -MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) -MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6) - -HTML_REF_MAN_FILE = $(HTMLDIR)/index.html - -TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf - -SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) - TOP_SPECS_FILE = specs.xml -# ---------------------------------------------------- -# FLAGS -# ---------------------------------------------------- -XML_FLAGS += -DVIPS_FLAGS += - -#SPECS_FLAGS = -I../../include -I../../../kernel/include -SPECS_FLAGS = -I../../../public_key/include -I../../../public_key/src -I../../.. +NO_CHUNKS = ssh_client_key_api.xml ssh_server_key_api.xml ssh_server_channel.xml # ---------------------------------------------------- -# Targets -# ---------------------------------------------------- -$(HTMLDIR)/%.png: %.png - $(INSTALL_DATA) $< $@ - -docs: pdf html man - -$(TOP_PDF_FILE): $(XML_FILES) - -images: $(IMAGE_FILES:%=$(HTMLDIR)/%) - -pdf: $(TOP_PDF_FILE) - -html: images $(HTML_REF_MAN_FILE) - - -clean clean_docs: - rm -rf $(HTMLDIR)/* - rm -rf $(XMLDIR) - rm -f $(MAN3DIR)/* - rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) - rm -f $(SPECS_FILES) - rm -f errs core *~ - -man: $(MAN3_FILES) $(MAN6_FILES) - - -debug opt: - - -# ---------------------------------------------------- -# Release Target -# ---------------------------------------------------- -include $(ERL_TOP)/make/otp_release_targets.mk - -release_docs_spec: docs - $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" - $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" - $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - $(INSTALL_DATA) $(HTMLDIR)/* \ - "$(RELSYSDIR)/doc/html" - $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" - $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" - $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" - $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6" - $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6" - - -release_spec: +include $(ERL_TOP)/make/doc.mk diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 862f79ac56..913b162fe9 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -91,7 +91,7 @@ <section> <title>Keys and files</title> <p>A number of objects must be present for the SSH application to work. - Thoose objects are per default stored in files. + Those objects are per default stored in files. The default names, paths and file formats are the same as for <url href="http://www.openssh.com">OpenSSH</url>. Keys could be generated with the <c>ssh-keygen</c> program from OpenSSH. See the @@ -746,6 +746,25 @@ </desc> </datatype> + <datatype> + <name name="tcpip_tunnel_in_daemon_option"/> + <desc> + <p>Enables (<c>true</c>) or disables (<c>false</c>) the possibility to tunnel a TCP/IP connection in to a + <seealso marker="ssh:ssh#daemon-2">server</seealso>. + Disabled per default. + </p> + </desc> + </datatype> + + <datatype> + <name name="tcpip_tunnel_out_daemon_option"/> + <desc> + <p>Enables (<c>true</c>) or disables (<c>false</c>) the possibility to tunnel a TCP/IP connection out of a + <seealso marker="ssh:ssh#daemon-2">server</seealso>. + Disabled per default. + </p> + </desc> + </datatype> <!--................................................................--> <datatype_title>Options common to clients and daemons</datatype_title> @@ -1124,9 +1143,9 @@ </datatype> <datatype> - <name>opaque_client_options</name> - <name>opaque_daemon_options</name> - <name>opaque_common_options</name> + <name>opaque_client_options()</name> + <name>opaque_daemon_options()</name> + <name>opaque_common_options()</name> <desc> <marker id="type-opaque_client_options"/> <marker id="type-opaque_daemon_options"/> @@ -1376,6 +1395,49 @@ </desc> </func> + <func> + <name name="tcpip_tunnel_from_server" arity="5" since=""/> + <name name="tcpip_tunnel_from_server" arity="6" since=""/> + <fsummary>TCP/IP tunneling from a server to a client ("tcpip-forward")</fsummary> + <desc> + <p>Asks the remote server of <c>ConnectionRef</c> to listen to <c>ListenHost:ListenPort</c>. + When someone connects that address, the connection is forwarded in an encrypted channel from + the server to the client. The client (that is, at the node that calls this function) then + connects to <c>ConnectToHost:ConnectToPort</c>. + </p> + <p>The returned <c>TrueListenPort</c> is the port that is listened to. It is the same as + <c>ListenPort</c>, except when <c>ListenPort = 0</c>. In that case a free port is selected + by the underlying OS. + </p> + <p>Note that in case of an Erlang/OTP SSH server (daemon) as peer, that server must have been + started with the option + <seealso marker="#type-tcpip_tunnel_out_daemon_option">tcpip_tunnel_out</seealso> + to allow the connection. + </p> + </desc> + </func> + + <func> + <name name="tcpip_tunnel_to_server" arity="5" since=""/> + <name name="tcpip_tunnel_to_server" arity="6" since=""/> + <fsummary>TCP/IP tunneling from a client to a server ("direct-tcpip")</fsummary> + <desc> + <p>Tells the local client to listen to <c>ListenHost:ListenPort</c>. When someone + connects to that address, the connection is forwarded in an encrypted channel to the peer server + of <c>ConnectionRef</c>. That server then connects to <c>ConnectToHost:ConnectToPort</c>. + </p> + <p>The returned <c>TrueListenPort</c> is the port that is listened to. It is the same as + <c>ListenPort</c>, except when <c>ListenPort = 0</c>. In that case a free port is selected + by the underlying OS. + </p> + <p>Note that in case of an Erlang/OTP SSH server (daemon) as peer, that server must have been + started with the option + <seealso marker="#type-tcpip_tunnel_in_daemon_option">tcpip_tunnel_in</seealso> + to allow the connection. + </p> + </desc> + </func> + </funcs> </erlref> diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index f5c520f2f0..68d7fd13e7 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -61,6 +61,7 @@ MODULES= \ ssh_app \ ssh_auth\ ssh_bits \ + ssh_channel_sup \ ssh_cli \ ssh_connection \ ssh_connection_handler \ @@ -71,7 +72,6 @@ MODULES= \ ssh_message \ ssh_no_io \ ssh_options \ - ssh_server_channel_sup \ ssh_sftp \ ssh_sftpd \ ssh_sftpd_file\ @@ -79,6 +79,10 @@ MODULES= \ ssh_subsystem_sup \ ssh_sup \ ssh_system_sup \ + ssh_tcpip_forward_srv \ + ssh_tcpip_forward_client \ + ssh_tcpip_forward_acceptor_sup \ + ssh_tcpip_forward_acceptor \ ssh_transport \ ssh_xfer \ sshc_sup \ @@ -180,7 +184,7 @@ $(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \ $(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl $(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl $(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl -$(EBIN)/ssh_server_channel_sup.$(EMULATOR): ssh_server_channel_sup.erl +$(EBIN)/ssh_channel_sup.$(EMULATOR): ssh_channel_sup.erl ssh.hrl $(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl $(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl $(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 21e3604400..fda507727a 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -11,6 +11,7 @@ ssh_auth, ssh_message, ssh_bits, + ssh_channel_sup, ssh_cli, ssh_client_channel, ssh_client_key_api, @@ -28,7 +29,6 @@ ssh_info, ssh_no_io, ssh_server_channel, - ssh_server_channel_sup, ssh_server_key_api, ssh_sftp, ssh_sftpd, @@ -36,6 +36,10 @@ ssh_sftpd_file_api, ssh_subsystem_sup, ssh_sup, + ssh_tcpip_forward_client, + ssh_tcpip_forward_srv, + ssh_tcpip_forward_acceptor_sup, + ssh_tcpip_forward_acceptor, ssh_system_sup, ssh_transport, ssh_xfer]}, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 355b40eea8..2673f30774 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -40,7 +40,9 @@ chk_algos_opts/1, stop_listener/1, stop_listener/2, stop_listener/3, stop_daemon/1, stop_daemon/2, stop_daemon/3, - shell/1, shell/2, shell/3 + shell/1, shell/2, shell/3, + tcpip_tunnel_from_server/5, tcpip_tunnel_from_server/6, + tcpip_tunnel_to_server/5, tcpip_tunnel_to_server/6 ]). %%% "Deprecated" types export: @@ -129,15 +131,13 @@ connect(Socket, UserOptions, NegotiationTimeout) when is_port(Socket), {error, Error} -> {error, Error}; Options -> - case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of - ok -> - {ok, {Host,_Port}} = inet:peername(Socket), - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), - ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout); - {error,SockError} -> - {error,SockError} - end - end; + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of + ok -> + connect_socket(Socket, Options, NegotiationTimeout); + {error,SockError} -> + {error,SockError} + end + end; connect(Host, Port, Options) when is_integer(Port), Port>0, @@ -151,9 +151,9 @@ connect(Host, Port, Options) when is_integer(Port), Options :: client_options(), NegotiationTimeout :: timeout(). -connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), - Port>0, - is_list(UserOptions) -> +connect(Host0, Port, UserOptions, NegotiationTimeout) when is_integer(Port), + Port>0, + is_list(UserOptions) -> case ssh_options:handle_options(client, UserOptions) of {error, _Reason} = Error -> Error; @@ -164,8 +164,7 @@ connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), Host = mangle_connect_address(Host0, SocketOpts), try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of {ok, Socket} -> - Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), - ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); + connect_socket(Socket, Options, NegotiationTimeout); {error, Reason} -> {error, Reason} catch @@ -176,6 +175,22 @@ connect(Host0, Port, UserOptions, Timeout) when is_integer(Port), end end. + +connect_socket(Socket, Options0, NegotiationTimeout) -> + {ok, {Host,Port}} = inet:sockname(Socket), + Profile = ?GET_OPT(profile, Options0), + {ok, SystemSup} = sshc_sup:start_child(Host, Port, Profile, Options0), + {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, client, Host, Port, Profile, Options0), + ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup), + Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, + {host,Host}, + {supervisors, [{system_sup, SystemSup}, + {subsystem_sup, SubSysSup}, + {connection_sup, ConnectionSup}]} + ], Options0), + ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout). + + %%-------------------------------------------------------------------- -spec close(ConnectionRef) -> ok | {error,term()} when ConnectionRef :: connection_ref() . @@ -452,7 +467,7 @@ stop_listener(Address, Port, Profile) -> -spec stop_daemon(DaemonRef::daemon_ref()) -> ok. stop_daemon(SysSup) -> - ssh_system_sup:stop_system(SysSup). + ssh_system_sup:stop_system(server, SysSup). -spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok. @@ -465,11 +480,11 @@ stop_daemon(Address, Port) -> stop_daemon(any, Port, Profile) -> map_ip(fun(IP) -> - ssh_system_sup:stop_system(IP, Port, Profile) + ssh_system_sup:stop_system(server, IP, Port, Profile) end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]); stop_daemon(Address, Port, Profile) -> map_ip(fun(IP) -> - ssh_system_sup:stop_system(IP, Port, Profile) + ssh_system_sup:stop_system(server, IP, Port, Profile) end, {address,Address}). %%-------------------------------------------------------------------- @@ -581,6 +596,113 @@ get_sock_opts(ConnectionRef, SocketGetOptions) -> ssh_connection_handler:get_sock_opts(ConnectionRef, SocketGetOptions). %%-------------------------------------------------------------------- +%% Ask local client to listen to ListenHost:ListenPort. When someone +%% connects that address, connect to ConnectToHost:ConnectToPort from +%% the server. +%%-------------------------------------------------------------------- +-spec tcpip_tunnel_to_server(ConnectionRef, + ListenHost, ListenPort, + ConnectToHost, ConnectToPort + ) -> + {ok,TrueListenPort} | {error, term()} when + ConnectionRef :: connection_ref(), + ListenHost :: host(), + ListenPort :: inet:port_number(), + ConnectToHost :: host(), + ConnectToPort :: inet:port_number(), + TrueListenPort :: inet:port_number(). + +tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort) -> + tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity). + + +-spec tcpip_tunnel_to_server(ConnectionRef, + ListenHost, ListenPort, + ConnectToHost, ConnectToPort, + Timeout) -> + {ok,TrueListenPort} | {error, term()} when + ConnectionRef :: connection_ref(), + ListenHost :: host(), + ListenPort :: inet:port_number(), + ConnectToHost :: host(), + ConnectToPort :: inet:port_number(), + Timeout :: timeout(), + TrueListenPort :: inet:port_number(). + +tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost0, ConnectToPort, Timeout) -> + SockOpts = [], + try + list_to_binary( + case mangle_connect_address(ConnectToHost0,SockOpts) of + IP when is_tuple(IP) -> inet_parse:ntoa(IP); + _ when is_list(ConnectToHost0) -> ConnectToHost0 + end) + of + ConnectToHost -> + ssh_connection_handler:handle_direct_tcpip(ConnectionHandler, + mangle_tunnel_address(ListenHost), ListenPort, + ConnectToHost, ConnectToPort, + Timeout) + catch + _:_ -> + {error, bad_connect_to_address} + end. + +%%-------------------------------------------------------------------- +%% Ask remote server to listen to ListenHost:ListenPort. When someone +%% connects that address, connect to ConnectToHost:ConnectToPort from +%% the client. +%%-------------------------------------------------------------------- +-spec tcpip_tunnel_from_server(ConnectionRef, + ListenHost, ListenPort, + ConnectToHost, ConnectToPort + ) -> + {ok,TrueListenPort} | {error, term()} when + ConnectionRef :: connection_ref(), + ListenHost :: host(), + ListenPort :: inet:port_number(), + ConnectToHost :: host(), + ConnectToPort :: inet:port_number(), + TrueListenPort :: inet:port_number(). + +tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort) -> + tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity). + +-spec tcpip_tunnel_from_server(ConnectionRef, + ListenHost, ListenPort, + ConnectToHost, ConnectToPort, + Timeout) -> + {ok,TrueListenPort} | {error, term()} when + ConnectionRef :: connection_ref(), + ListenHost :: host(), + ListenPort :: inet:port_number(), + ConnectToHost :: host(), + ConnectToPort :: inet:port_number(), + Timeout :: timeout(), + TrueListenPort :: inet:port_number(). + +tcpip_tunnel_from_server(ConnectionRef, ListenHost0, ListenPort, ConnectToHost0, ConnectToPort, Timeout) -> + SockOpts = [], + ListenHost = mangle_tunnel_address(ListenHost0), + ConnectToHost = mangle_connect_address(ConnectToHost0, SockOpts), + case ssh_connection_handler:global_request(ConnectionRef, "tcpip-forward", true, + {ListenHost,ListenPort,ConnectToHost,ConnectToPort}, + Timeout) of + {success,<<>>} -> + {ok, ListenPort}; + {success,<<TruePort:32/unsigned-integer>>} when ListenPort==0 -> + {ok, TruePort}; + {success,_} = Res -> + {error, {bad_result,Res}}; + {failure,<<>>} -> + {error,not_accepted}; + {failure,Error} -> + {error,Error}; + Other -> + Other + end. + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- %% The handle_daemon_args/2 function basically only sets the ip-option in Opts @@ -697,3 +819,16 @@ mangle_connect_address1(A, _) -> {ok, {0,0,0,0,0,0,0,0}} -> loopback(true); _ -> A end. + +%%%---------------------------------------------------------------- +mangle_tunnel_address(any) -> <<"">>; +mangle_tunnel_address(loopback) -> <<"localhost">>; +mangle_tunnel_address({0,0,0,0}) -> <<"">>; +mangle_tunnel_address({0,0,0,0,0,0,0,0}) -> <<"">>; +mangle_tunnel_address(IP) when is_tuple(IP) -> list_to_binary(inet_parse:ntoa(IP)); +mangle_tunnel_address(A) when is_atom(A) -> mangle_tunnel_address(atom_to_list(A)); +mangle_tunnel_address(X) when is_list(X) -> case catch inet:parse_address(X) of + {ok, {0,0,0,0}} -> <<"">>; + {ok, {0,0,0,0,0,0,0,0}} -> <<"">>; + _ -> list_to_binary(X) + end. diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index a9d81f7252..e754b9ebc6 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -51,6 +51,7 @@ -define(STRING(X), ?UINT32((size(X))), (X)/binary). -define(DEC_BIN(X,Len), ?UINT32(Len), X:Len/binary ). +-define(DEC_INT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ). -define(DEC_MPINT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ). %% building macros @@ -308,6 +309,8 @@ | shell_daemon_option() | exec_daemon_option() | ssh_cli_daemon_option() + | tcpip_tunnel_out_daemon_option() + | tcpip_tunnel_in_daemon_option() | authentication_daemon_options() | diffie_hellman_group_exchange_daemon_option() | negotiation_timeout_daemon_option() @@ -338,6 +341,9 @@ -type ssh_cli_daemon_option() :: {ssh_cli, mod_args() | no_cli }. +-type tcpip_tunnel_out_daemon_option() :: {tcpip_tunnel_out, boolean()} . +-type tcpip_tunnel_in_daemon_option() :: {tcpip_tunnel_in, boolean()} . + -type send_ext_info_daemon_option() :: {send_ext_info, boolean()} . -type authentication_daemon_options() :: diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index b9813b6b5c..bb3bec9c7c 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -143,9 +143,9 @@ get_public_key(SigAlg, #ssh{opts = Opts}) -> {ok, PrivKey} -> try %% Check the key - the KeyCb may be a buggy plugin - true = ssh_transport:valid_key_sha_alg(PrivKey, KeyAlg), + true = ssh_transport:valid_key_sha_alg(private, PrivKey, KeyAlg), Key = ssh_transport:extract_public_key(PrivKey), - public_key:ssh_encode(Key, ssh2_pubkey) + ssh_message:ssh2_pubkey_encode(Key) of PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}} catch @@ -495,7 +495,7 @@ get_password_option(Opts, User) -> pre_verify_sig(User, KeyBlob, Opts) -> try - Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts) catch _:_ -> @@ -505,7 +505,7 @@ pre_verify_sig(User, KeyBlob, Opts) -> verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts = Opts} = Ssh) -> try Alg = binary_to_list(AlgBin), - Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception + Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception true = ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts), PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen, diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl new file mode 100644 index 0000000000..4b36c8a5a0 --- /dev/null +++ b/lib/ssh/src/ssh_channel_sup.erl @@ -0,0 +1,90 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Ssh channel supervisor. +%%---------------------------------------------------------------------- +-module(ssh_channel_sup). + +-behaviour(supervisor). +-include("ssh.hrl"). + +-export([start_link/1, start_child/8]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% Internal API +%%%========================================================================= +start_link(Args) -> + supervisor:start_link(?MODULE, [Args]). + + +start_child(client, ChannelSup, ConnRef, Callback, Id, Args, Exec, _Opts) when is_pid(ConnRef) -> + start_the_child(ssh_client_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec); +start_child(server, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts) when is_pid(ConnRef) -> + case max_num_channels_not_exceeded(ChannelSup, Opts) of + true -> + start_the_child(ssh_server_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec); + false -> + {error, max_num_channels_exceeded} + end. + + +start_the_child(ChanMod, ChannelSup, ConnRef, Callback, Id, Args, Exec) -> + ChildSpec = + #{id => make_ref(), + start => {ChanMod, start_link, [ConnRef, Id, Callback, Args, Exec]}, + restart => temporary, + type => worker, + modules => [ChanMod] + }, + case supervisor:start_child(ChannelSup, ChildSpec) of + {ok, Pid} -> + {ok, Pid}; + {ok, Pid, _Info} -> + {ok,Pid}; + {error, {Error,_Info}} -> + {error, Error}; + {error, Error} -> + {error, Error} + end. + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_Args) -> + RestartStrategy = one_for_one, + MaxR = 10, + MaxT = 3600, + Children = [], + {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. + +%%%========================================================================= +%%% Internal functions +%%%========================================================================= +max_num_channels_not_exceeded(ChannelSup, Opts) -> + MaxNumChannels = ?GET_OPT(max_channels, Opts), + NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <- + supervisor:which_children(ChannelSup)]), + %% Note that NumChannels is BEFORE starting a new one + NumChannels < MaxNumChannels. diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 380faeb11e..0d5ffaa509 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -45,6 +45,8 @@ handle_msg/3, handle_stop/1, + open_channel/4, + channel_adjust_window_msg/2, channel_close_msg/1, channel_open_failure_msg/4, @@ -57,6 +59,7 @@ channel_request_msg/4, channel_success_msg/1, + request_global_msg/3, request_failure_msg/0, request_success_msg/1, @@ -202,10 +205,22 @@ session_channel(ConnectionHandler, Timeout) -> Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> - case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, - InitialWindowSize, - MaxPacketSize, Timeout) of - {open, Channel} -> + open_channel(ConnectionHandler, "session", <<>>, + InitialWindowSize, + MaxPacketSize, + Timeout). + +%%-------------------------------------------------------------------- +%% Description: Opens a channel for the given type. +%% -------------------------------------------------------------------- +open_channel(ConnectionHandler, Type, ChanData, Timeout) -> + open_channel(ConnectionHandler, Type, ChanData, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). + +open_channel(ConnectionHandler, Type, ChanData, InitialWindowSize, MaxPacketSize, Timeout) -> + case ssh_connection_handler:open_channel(ConnectionHandler, Type, ChanData, + InitialWindowSize, MaxPacketSize, + Timeout) of + {open, Channel} -> {ok, Channel}; Error -> Error @@ -376,6 +391,7 @@ ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> proplists:get_value(pixel_height, TermData, PixHeight), proplists:get_value(pty_opts, TermData, []), TimeOut ). + %%-------------------------------------------------------------------- %% Not yet officialy supported! The following functions are part of the %% initial contributed ssh application. They are untested. Do we want them? @@ -566,6 +582,124 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type, {[{connection_reply, FailMsg}], Connection0} end; +handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip", + sender_channel = RemoteId, + initial_window_size = WindowSize, + maximum_packet_size = PacketSize, + data = <<?DEC_BIN(ConnectedHost,_L1), ?UINT32(ConnectedPort), + ?DEC_BIN(_OriginHost,_L2), ?UINT32(_OriginPort) + >> + }, + #connection{channel_cache = Cache, + channel_id_seed = ChId, + options = Options, + sub_system_supervisor = SubSysSup + } = C, + client) -> + {ReplyMsg, NextChId} = + case ssh_connection_handler:retrieve(C, {tcpip_forward,ConnectedHost,ConnectedPort}) of + {ok, {ConnectToHost,ConnectToPort}} -> + case gen_tcp:connect(ConnectToHost, ConnectToPort, [{active,false}, binary]) of + {ok,Sock} -> + {ok,Pid} = ssh_subsystem_sup:start_channel(client, SubSysSup, self(), + ssh_tcpip_forward_client, ChId, + [Sock], undefined, Options), + ssh_client_channel:cache_update(Cache, + #channel{type = "forwarded-tcpip", + sys = "none", + local_id = ChId, + remote_id = RemoteId, + user = Pid, + recv_window_size = ?DEFAULT_WINDOW_SIZE, + recv_packet_size = ?DEFAULT_PACKET_SIZE, + send_window_size = WindowSize, + send_packet_size = PacketSize, + send_buf = queue:new() + }), + gen_tcp:controlling_process(Sock, Pid), + inet:setopts(Sock, [{active,once}]), + {channel_open_confirmation_msg(RemoteId, ChId, + ?DEFAULT_WINDOW_SIZE, + ?DEFAULT_PACKET_SIZE), + ChId + 1}; + + {error,Error} -> + {channel_open_failure_msg(RemoteId, + ?SSH_OPEN_CONNECT_FAILED, + io_lib:format("Forwarded connection refused: ~p",[Error]), + "en"), + ChId} + end; + + undefined -> + {channel_open_failure_msg(RemoteId, + ?SSH_OPEN_CONNECT_FAILED, + io_lib:format("No forwarding ordered",[]), + "en"), + ChId} + end, + {[{connection_reply, ReplyMsg}], C#connection{channel_id_seed = NextChId}}; + +handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip", + sender_channel = RemoteId, + initial_window_size = WindowSize, + maximum_packet_size = PacketSize, + data = <<?DEC_BIN(HostToConnect,_L1), ?UINT32(PortToConnect), + ?DEC_BIN(_OriginatorIPaddress,_L2), ?UINT32(_OrignatorPort) + >> + }, + #connection{channel_cache = Cache, + channel_id_seed = ChId, + options = Options, + sub_system_supervisor = SubSysSup + } = C, + server) -> + {ReplyMsg, NextChId} = + case ?GET_OPT(tcpip_tunnel_in, Options) of + %% May add more to the option, like allowed ip/port pairs to connect to + false -> + {channel_open_failure_msg(RemoteId, + ?SSH_OPEN_CONNECT_FAILED, + "Forwarding disabled", "en"), + ChId}; + + true -> + case gen_tcp:connect(binary_to_list(HostToConnect), PortToConnect, + [{active,false}, binary]) of + {ok,Sock} -> + {ok,Pid} = ssh_subsystem_sup:start_channel(server, SubSysSup, self(), + ssh_tcpip_forward_srv, ChId, + [Sock], undefined, Options), + ssh_client_channel:cache_update(Cache, + #channel{type = "direct-tcpip", + sys = "none", + local_id = ChId, + remote_id = RemoteId, + user = Pid, + recv_window_size = ?DEFAULT_WINDOW_SIZE, + recv_packet_size = ?DEFAULT_PACKET_SIZE, + send_window_size = WindowSize, + send_packet_size = PacketSize, + send_buf = queue:new() + }), + gen_tcp:controlling_process(Sock, Pid), + inet:setopts(Sock, [{active,once}]), + + {channel_open_confirmation_msg(RemoteId, ChId, + ?DEFAULT_WINDOW_SIZE, + ?DEFAULT_PACKET_SIZE), + ChId + 1}; + + {error,Error} -> + {channel_open_failure_msg(RemoteId, + ?SSH_OPEN_CONNECT_FAILED, + io_lib:format("Forwarded connection refused: ~p",[Error]), + "en"), + ChId} + end + end, + {[{connection_reply, ReplyMsg}], C#connection{channel_id_seed = NextChId}}; + handle_msg(#ssh_msg_channel_open{channel_type = "session", sender_channel = RemoteId}, Connection, @@ -646,19 +780,14 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, #channel{remote_id=RemoteId} = Channel = ssh_client_channel:cache_lookup(Cache, ChannelId), Reply = - try - start_subsystem(SsName, Connection, Channel, - {subsystem, ChannelId, WantReply, binary_to_list(SsName)}) - of + case start_subsystem(SsName, Connection, Channel, + {subsystem, ChannelId, WantReply, binary_to_list(SsName)}) of {ok, Pid} -> erlang:monitor(process, Pid), ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}), channel_success_msg(RemoteId); {error,_Error} -> channel_failure_msg(RemoteId) - catch - _:_ -> - channel_failure_msg(RemoteId) end, {[{connection_reply,Reply}], Connection}; @@ -743,9 +872,45 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId, {[], Connection} end; +handle_msg(#ssh_msg_global_request{name = <<"tcpip-forward">>, + want_reply = WantReply, + data = <<?DEC_BIN(ListenAddrStr,_Len),?UINT32(ListenPort)>>}, + #connection{options = Opts} = Connection, server) -> + case ?GET_OPT(tcpip_tunnel_out, Opts) of + false -> + %% This daemon instance has not enabled tcpip_forwarding + {[{connection_reply, request_failure_msg()}], Connection}; + + true -> + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + SubSysSup = proplists:get_value(subsystem_sup, Sups), + FwdSup = ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup), + ConnPid = self(), + case ssh_tcpip_forward_acceptor:supervised_start(FwdSup, + {ListenAddrStr, ListenPort}, + undefined, + "forwarded-tcpip", ssh_tcpip_forward_srv, + ConnPid) of + {ok,ListenPort} when WantReply==true -> + {[{connection_reply, request_success_msg(<<>>)}], Connection}; + + {ok,LPort} when WantReply==true -> + {[{connection_reply, request_success_msg(<<?UINT32(LPort)>>)}], Connection}; + + {error,_} when WantReply==true -> + {[{connection_reply, request_failure_msg()}], Connection}; + + _ when WantReply==true -> + {[{connection_reply, request_failure_msg()}], Connection}; + + _ -> + {[], Connection} + end + end; + handle_msg(#ssh_msg_global_request{name = _Type, want_reply = WantReply, - data = _Data}, Connection, _) -> + data = _Data}, Connection, _Role) -> if WantReply == true -> FailMsg = request_failure_msg(), {[{connection_reply, FailMsg}], Connection}; @@ -758,11 +923,22 @@ handle_msg(#ssh_msg_request_failure{}, {[{channel_request_reply, From, {failure, <<>>}}], Connection#connection{requests = Rest}}; +handle_msg(#ssh_msg_request_failure{}, + #connection{requests = [{_, From,_} | Rest]} = Connection, _) -> + {[{channel_request_reply, From, {failure, <<>>}}], + Connection#connection{requests = Rest}}; + handle_msg(#ssh_msg_request_success{data = Data}, #connection{requests = [{_, From} | Rest]} = Connection, _) -> {[{channel_request_reply, From, {success, Data}}], Connection#connection{requests = Rest}}; +handle_msg(#ssh_msg_request_success{data = Data}, + #connection{requests = [{_, From, Fun} | Rest]} = Connection0, _) -> + Connection = Fun({success,Data}, Connection0), + {[{channel_request_reply, From, {success, Data}}], + Connection#connection{requests = Rest}}; + handle_msg(#ssh_msg_disconnect{code = Code, description = Description}, Connection, _) -> @@ -847,8 +1023,13 @@ channel_success_msg(ChannelId) -> %%%---------------------------------------------------------------- %%% request_*_msg(...) -%%% Returns a #ssh_msg_....{} for request responses. +%%% Returns a #ssh_msg_....{} %%% +request_global_msg(Name, WantReply, Data) -> + #ssh_msg_global_request{name = Name, + want_reply = WantReply, + data = Data}. + request_failure_msg() -> #ssh_msg_request_failure{}. @@ -919,7 +1100,7 @@ start_cli(#connection{options = Options, no_cli -> {error, cli_disabled}; {CbModule, Args} -> - start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options) + ssh_subsystem_sup:start_channel(server, SubSysSup, self(), CbModule, ChannelId, Args, Exec, Options) end. @@ -929,37 +1110,15 @@ start_subsystem(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, Options); - {Other, _} when Other =/= none -> + ssh_subsystem_sup:start_channel(server, SubSysSup, self(), Callback, ChannelId, Opts, undefined, Options); + {none, _} -> + {error, bad_subsystem}; + {_, _} -> {error, legacy_option_not_supported} end. %%% Helpers for starting cli/subsystems -start_channel(Cb, Id, Args, SubSysSup, Opts) -> - start_channel(Cb, Id, Args, SubSysSup, undefined, Opts). - -start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) -> - ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup), - case max_num_channels_not_exceeded(ChannelSup, Opts) of - true -> - case ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec) of - {error,{Error,_Info}} -> - throw(Error); - Others -> - Others - end; - false -> - throw(max_num_channels_exceeded) - end. - -max_num_channels_not_exceeded(ChannelSup, Opts) -> - MaxNumChannels = ?GET_OPT(max_channels, Opts), - NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <- - supervisor:which_children(ChannelSup)]), - %% Note that NumChannels is BEFORE starting a new one - NumChannels < MaxNumChannels. - check_subsystem("sftp"= SsName, Options) -> case ?GET_OPT(subsystems, Options) of no_subsys -> % FIXME: Can 'no_subsys' ever be matched? @@ -1298,13 +1457,13 @@ handle_cli_msg(C0, ChId, Reply0) -> Ch0 = ssh_client_channel:cache_lookup(Cache, ChId), case Ch0#channel.user of undefined -> - case (catch start_cli(C0, ChId)) of + case start_cli(C0, ChId) of {ok, Pid} -> erlang:monitor(process, Pid), Ch = Ch0#channel{user = Pid}, ssh_client_channel:cache_update(Cache, Ch), reply_msg(Ch, C0, Reply0); - _Other -> + {error, _Error} -> Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)}, {[Reply], C0} end; @@ -1315,6 +1474,10 @@ handle_cli_msg(C0, ChId, Reply0) -> %%%---------------------------------------------------------------- %%% +%%% TCP/IP forwarding + +%%%---------------------------------------------------------------- +%%% %%% Request response handling on return to the calling ssh_connection_handler %%% state machine. %%% diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e8c0d88e59..d34537950e 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -48,10 +48,15 @@ -export([start_connection/4, available_hkey_algorithms/2, open_channel/6, + start_channel/5, + handle_direct_tcpip/6, request/6, request/7, reply_request/3, + global_request/5, send/5, send_eof/2, + store/3, + retrieve/2, info/1, info/2, connection_info/2, channel_info/3, @@ -128,35 +133,29 @@ stop(ConnectionHandler)-> timeout() ) -> {ok, connection_ref()} | {error, term()}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -start_connection(client = Role, Socket, Options, Timeout) -> +start_connection(Role, Socket, Options, Timeout) -> try - {ok, Pid} = sshc_sup:start_child([Role, Socket, Options]), - ok = socket_control(Socket, Pid, Options), - handshake(Pid, erlang:monitor(process,Pid), Timeout) - catch - exit:{noproc, _} -> - {error, ssh_not_started}; - _:Error -> - {error, Error} - end; - -start_connection(server = Role, Socket, Options, Timeout) -> - try - case ?GET_OPT(parallel_login, Options) of - true -> - HandshakerPid = - spawn_link(fun() -> - receive - {do_handshake, Pid} -> - handshake(Pid, erlang:monitor(process,Pid), Timeout) - end - end), - ChildPid = start_the_connection_child(HandshakerPid, Role, Socket, Options), - HandshakerPid ! {do_handshake, ChildPid}; - false -> - ChildPid = start_the_connection_child(self(), Role, Socket, Options), - handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout) - end + case Role of + client -> + ChildPid = start_the_connection_child(self(), Role, Socket, Options), + handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout); + server -> + case ?GET_OPT(parallel_login, Options) of + true -> + HandshakerPid = + spawn_link(fun() -> + receive + {do_handshake, Pid} -> + handshake(Pid, erlang:monitor(process,Pid), Timeout) + end + end), + ChildPid = start_the_connection_child(HandshakerPid, Role, Socket, Options), + HandshakerPid ! {do_handshake, ChildPid}; + false -> + ChildPid = start_the_connection_child(self(), Role, Socket, Options), + handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout) + end + end catch exit:{noproc, _} -> {error, ssh_not_started}; @@ -178,6 +177,8 @@ disconnect(Code, DetailedText, Module, Line) -> [{next_event, internal, {send_disconnect, Code, DetailedText, Module, Line}}]}). %%-------------------------------------------------------------------- +%%% Open a channel in the connection to the peer, that is, do the ssh +%%% signalling with the peer. -spec open_channel(connection_ref(), string(), iodata(), @@ -197,6 +198,22 @@ open_channel(ConnectionHandler, Timeout}). %%-------------------------------------------------------------------- +%%% Start a channel handling process in the superviser tree +-spec start_channel(connection_ref(), atom(), channel_id(), list(), term()) -> + {ok, pid()} | {error, term()}. + +%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +start_channel(ConnectionHandler, CallbackModule, ChannelId, Args, Exec) -> + {ok, {SubSysSup,Role,Opts}} = call(ConnectionHandler, get_misc), + ssh_subsystem_sup:start_channel(Role, SubSysSup, + ConnectionHandler, CallbackModule, ChannelId, + Args, Exec, Opts). + +%%-------------------------------------------------------------------- +handle_direct_tcpip(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, Timeout) -> + call(ConnectionHandler, {handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, Timeout}). + +%%-------------------------------------------------------------------- -spec request(connection_ref(), pid(), channel_id(), @@ -235,6 +252,12 @@ reply_request(ConnectionHandler, Status, ChannelId) -> cast(ConnectionHandler, {reply_request, Status, ChannelId}). %%-------------------------------------------------------------------- +global_request(ConnectionHandler, Type, true, Data, Timeout) -> + call(ConnectionHandler, {global_request, Type, Data, Timeout}); +global_request(ConnectionHandler, Type, false, Data, _) -> + cast(ConnectionHandler, {global_request, Type, Data}). + +%%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), non_neg_integer(), @@ -324,6 +347,21 @@ close(ConnectionHandler, ChannelId) -> %%-------------------------------------------------------------------- +store(ConnectionHandler, Key, Value) -> + cast(ConnectionHandler, {store,Key,Value}). + +retrieve(#connection{options=Opts}, Key) -> + try ?GET_INTERNAL_OPT(Key, Opts) of + Value -> + {ok,Value} + catch + error:{badkey,Key} -> + undefined + end; +retrieve(ConnectionHandler, Key) -> + call(ConnectionHandler, {retrieve,Key}). + +%%-------------------------------------------------------------------- %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . set_sock_opts(ConnectionRef, SocketOptions) -> try lists:foldr(fun({Name,_Val}, Acc) -> @@ -408,13 +446,26 @@ alg(ConnectionHandler) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . init_connection_handler(Role, Socket, Opts) -> case init([Role, Socket, Opts]) of - {ok, StartState, D} -> + {ok, StartState, D} when Role == server -> process_flag(trap_exit, true), gen_statem:enter_loop(?MODULE, [], %%[{debug,[trace,log,statistics,debug]} ], %% [] StartState, D); + {ok, StartState, D0=#data{connection_state=C}} when Role == client -> + process_flag(trap_exit, true), + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + D = D0#data{connection_state = + 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, + [], %%[{debug,[trace,log,statistics,debug]} ], %% [] + StartState, + D); + {stop, Error} -> D = try %% Only servers have supervisorts defined in Opts @@ -1183,6 +1234,10 @@ handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) -> Msg = #ssh_msg_unimplemented{sequence = Data}, {keep_state, send_msg(Msg,D)}; +handle_event(cast, {global_request, Type, Data}, StateName, D) when ?CONNECTED(StateName) -> + {keep_state, send_msg(ssh_connection:request_global_msg(Type,false,Data), D)}; + + %%% Previously handle_sync_event began here handle_event({call,From}, get_print_info, StateName, D) -> Reply = @@ -1274,6 +1329,34 @@ handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, {keep_state, D, cond_set_idle_timer(D)} end; +handle_event({call,From}, {global_request, "tcpip-forward" = Type, + {ListenHost,ListenPort,ConnectToHost,ConnectToPort}, + Timeout}, StateName, D0) when ?CONNECTED(StateName) -> + Id = make_ref(), + Data = <<?STRING(ListenHost), ?Euint32(ListenPort)>>, + Fun = fun({success, <<Port:32/unsigned-integer>>}, C) -> + Key = {tcpip_forward,ListenHost,Port}, + Value = {ConnectToHost,ConnectToPort}, + C#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C#connection.options)}; + ({success, <<>>}, C) -> + Key = {tcpip_forward,ListenHost,ListenPort}, + Value = {ConnectToHost,ConnectToPort}, + C#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C#connection.options)}; + (_, C) -> + C + end, + D = send_msg(ssh_connection:request_global_msg(Type, true, Data), + add_request(Fun, Id, From, D0)), + start_channel_request_timer(Id, From, Timeout), + {keep_state, D, cond_set_idle_timer(D)}; + +handle_event({call,From}, {global_request, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> + Id = make_ref(), + D = send_msg(ssh_connection:request_global_msg(Type, true, Data), + add_request(true, Id, From, D0)), + start_channel_request_timer(Id, From, Timeout), + {keep_state, D, cond_set_idle_timer(D)}; + handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) -> {Repls,D} = send_replies(ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From), @@ -1291,6 +1374,13 @@ handle_event({call,From}, {eof, ChannelId}, StateName, D0) {keep_state, D0, [{reply,From,{error,closed}}]} end; +handle_event({call,From}, get_misc, StateName, + #data{connection_state = #connection{options = Opts}} = D) when ?CONNECTED(StateName) -> + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + SubSysSup = proplists:get_value(subsystem_sup, Sups), + Reply = {ok, {SubSysSup, role(StateName), Opts}}, + {keep_state, D, [{reply,From,Reply}]}; + handle_event({call,From}, {open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout}, StateName, @@ -1347,6 +1437,17 @@ handle_event({call,From}, {close, ChannelId}, StateName, D0) {keep_state_and_data, [{reply,From,ok}]} end; +handle_event(cast, {store,Key,Value}, _StateName, #data{connection_state=C0} = D) -> + C = C0#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C0#connection.options)}, + {keep_state, D#data{connection_state = C}}; + +handle_event({call,From}, {retrieve,Key}, _StateName, #data{connection_state=C}) -> + case retrieve(C, Key) of + {ok,Value} -> + {keep_state_and_data, [{reply,From,{ok,Value}}]}; + _ -> + {keep_state_and_data, [{reply,From,undefined}]} + end; %%===== Reception of encrypted bytes, decryption and framing handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock, @@ -1515,6 +1616,32 @@ handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) -> handle_event(info, check_cache, _, D) -> {keep_state, D, cond_set_idle_timer(D)}; +handle_event(info, {fwd_connect_received, Sock, ChId, ChanCB}, StateName, #data{connection_state = Connection}) -> + #connection{options = Options, + channel_cache = Cache, + sub_system_supervisor = SubSysSup} = Connection, + Channel = ssh_client_channel:cache_lookup(Cache, ChId), + {ok,Pid} = ssh_subsystem_sup:start_channel(role(StateName), SubSysSup, self(), ChanCB, ChId, [Sock], undefined, Options), + ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}), + gen_tcp:controlling_process(Sock, Pid), + inet:setopts(Sock, [{active,once}]), + keep_state_and_data; + +handle_event({call,From}, + {handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, _Timeout}, + _StateName, + #data{connection_state = #connection{sub_system_supervisor=SubSysSup}}) -> + case ssh_tcpip_forward_acceptor:supervised_start(ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup), + {ListenHost, ListenPort}, + {ConnectToHost, ConnectToPort}, + "direct-tcpip", ssh_tcpip_forward_client, + self()) of + {ok,LPort} -> + {keep_state_and_data, [{reply,From,{ok,LPort}}]}; + {error,Error} -> + {keep_state_and_data, [{reply,From,{error,Error}}]} + end; + handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) -> case unexpected_fun(UnexpectedMessage, D) of report -> @@ -1705,17 +1832,36 @@ start_the_connection_child(UserPid, Role, Socket, Options0) -> Sups = ?GET_INTERNAL_OPT(supervisors, Options0), ConnectionSup = proplists:get_value(connection_sup, Sups), Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0), - {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Options]), - ok = socket_control(Socket, Pid, Options), + InitArgs = [Role, Socket, Options], + {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, InitArgs), + ok = socket_control(Socket, Pid, Options), % transfer the Socket ownership in a controlled way. Pid. %%-------------------------------------------------------------------- %% Stopping -stop_subsystem(#data{connection_state = +stop_subsystem(#data{ssh_params = + #ssh{role = Role}, + connection_state = #connection{system_supervisor = SysSup, - sub_system_supervisor = SubSysSup}}) when is_pid(SubSysSup) -> - ssh_system_sup:stop_subsystem(SysSup, SubSysSup); + sub_system_supervisor = SubSysSup} + }) when is_pid(SysSup) andalso is_pid(SubSysSup) -> + process_flag(trap_exit, false), + C = self(), + spawn(fun() -> + Mref = erlang:monitor(process, C), + receive + {'DOWN', Mref, process, C, _Info} -> ok + after + 10000 -> ok + end, + case Role of + client -> + ssh_system_sup:stop_system(Role, SysSup); + _ -> + ssh_system_sup:stop_subsystem(SysSup, SubSysSup) + end + end); stop_subsystem(_) -> ok. @@ -1807,6 +1953,8 @@ call(FsmPid, Event, Timeout) -> exit:{normal, _R} -> {error, closed}; exit:{{shutdown, _R},_} -> + {error, closed}; + exit:{shutdown, _R} -> {error, closed} end. @@ -1941,6 +2089,11 @@ add_request(true, ChannelId, From, #data{connection_state = #connection{requests = Requests0} = Connection} = State) -> Requests = [{ChannelId, From} | Requests0], + State#data{connection_state = Connection#connection{requests = Requests}}; +add_request(Fun, ChannelId, From, #data{connection_state = + #connection{requests = Requests0} = + Connection} = State) when is_function(Fun) -> + Requests = [{ChannelId, From, Fun} | Requests0], State#data{connection_state = Connection#connection{requests = Requests}}. new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 510269bbb1..a9034d2085 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -24,218 +24,140 @@ -module(ssh_file). --behaviour(ssh_server_key_api). --behaviour(ssh_client_key_api). - -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). -include("ssh.hrl"). --export([host_key/2, - user_key/2, - is_host_key/4, - add_host_key/3, - is_auth_key/3]). - - --export_type([system_dir_daemon_option/0, - user_dir_common_option/0, - user_dir_fun_common_option/0, - pubkey_passphrase_client_options/0 - ]). - +%%%--------------------- server exports --------------------------- +-behaviour(ssh_server_key_api). +-export([host_key/2, is_auth_key/3]). +-export_type([system_dir_daemon_option/0]). -type system_dir_daemon_option() :: {system_dir, string()}. --type user_dir_common_option() :: {user_dir, string()}. --type user_dir_fun_common_option() :: {user_dir_fun, user2dir()}. --type user2dir() :: fun((RemoteUserName::string()) -> UserDir :: string()) . +%%%--------------------- client exports --------------------------- +-behaviour(ssh_client_key_api). +-export([is_host_key/4, user_key/2, add_host_key/3]). +-export_type([pubkey_passphrase_client_options/0]). -type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()} | {rsa_pass_phrase, string()} %% Not yet implemented: | {ed25519_pass_phrase, string()} %% Not yet implemented: | {ed448_pass_phrase, string()} | {ecdsa_pass_phrase, string()} . +%%%--------------------- common exports --------------------------- +-export_type([user_dir_common_option/0, + user_dir_fun_common_option/0 + ]). --define(PERM_700, 8#700). --define(PERM_644, 8#644). - +-type user_dir_common_option() :: {user_dir, string()}. +-type user_dir_fun_common_option() :: {user_dir_fun, user2dir()}. +-type user2dir() :: fun((RemoteUserName::string()) -> UserDir :: string()) . +%%%================================================================ +%%% %%% API +%%% -%% Used by server +%%%---------------- SERVER API ------------------------------------ host_key(Algorithm, Opts) -> - File = file_name(system, file_base_name(Algorithm), Opts), - %% We do not expect host keys to have pass phrases - %% 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), - case decode(File, Password) of - {ok,Key} -> - check_key_type(Key, Algorithm); - {error,DecodeError} -> - {error,DecodeError} - end. - -is_auth_key(Key, User,Opts) -> - case lookup_user_key(Key, User, Opts) of - {ok, Key} -> - true; - _ -> - false - end. + read_ssh_key_file(system, private, Algorithm, Opts). +is_auth_key(Key, User ,Opts) -> + KeyType = erlang:atom_to_binary(ssh_transport:public_algo(Key), latin1), + Dir = ssh_dir({remoteuser,User}, Opts), + lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys")) + orelse + lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys2")). -%% Used by client -is_host_key(Key, PeerName, Algorithm, Opts) -> - case lookup_host_key(Key, PeerName, Algorithm, Opts) of - {ok, Key} -> - true; - _ -> - false - end. - +%%%---------------- CLIENT API ------------------------------------ user_key(Algorithm, Opts) -> - File = file_name(user, identity_key_filename(Algorithm), Opts), - Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), - case decode(File, Password) of - {ok, Key} -> - check_key_type(Key, Algorithm); - Error -> - Error - end. + read_ssh_key_file(user, private, Algorithm, Opts). - -%% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -check_key_type(Key, Algorithm) -> - case ssh_transport:valid_key_sha_alg(Key,Algorithm) of - true -> {ok,Key}; - false -> {error,bad_keytype_in_file} - end. - -file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key"; -file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key"; -file_base_name('rsa-sha2-384' ) -> "ssh_host_rsa_key"; -file_base_name('rsa-sha2-512' ) -> "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-ed25519' ) -> "ssh_host_ed25519_key"; -file_base_name('ssh-ed448' ) -> "ssh_host_ed448_key"; -file_base_name(_ ) -> "ssh_host_key". - -decode(File, Password) -> - try {ok, decode_ssh_file(read_ssh_file(File), Password)} - catch - throw:Reason -> - {error, Reason}; - error:Reason -> - {error, Reason} - end. - -read_ssh_file(File) -> - {ok, Bin} = file:read_file(File), - Bin. - -%% Public key -decode_ssh_file(SshBin, public_key) -> - public_key:ssh_decode(SshBin, public_key); - -%% Private Key -decode_ssh_file(Pem, Password) -> - case public_key:pem_decode(Pem) of - [{_, _, not_encrypted} = Entry] -> - public_key:pem_entry_decode(Entry); - [Entry] when Password =/= ignore -> - public_key:pem_entry_decode(Entry, Password); - _ -> - throw("No pass phrase provided for private key file") - end. - - -%% lookup_host_key -%% return {ok, Key(s)} or {error, not_found} -%% - -lookup_host_key(KeyToMatch, Host, Alg, Opts) -> - Host1 = replace_localhost(Host), - do_lookup_host_key(KeyToMatch, Host1, Alg, Opts). - +is_host_key(Key, PeerName, Algorithm, Opts) -> + KeyType = erlang:atom_to_binary(Algorithm, latin1), + Hosts = binary:split(list_to_binary(replace_localhost(PeerName)), + <<",">>, [global]), % make a list of hosts + Dir = ssh_dir(user, Opts), + lookup_host_keys(Hosts, KeyType, Key, filename:join(Dir,"known_hosts")). add_host_key(Host, Key, Opts) -> Host1 = add_ip(replace_localhost(Host)), KnownHosts = file_name(user, "known_hosts", Opts), case file:open(KnownHosts, [write,append]) of - {ok, Fd} -> - ok = file:change_mode(KnownHosts, ?PERM_644), - Res = add_key_fd(Fd, Host1, Key), - file:close(Fd), - Res; - Error -> - Error - end. - -lookup_user_key(Key, User, Opts) -> - SshDir = ssh_dir({remoteuser,User}, Opts), - case lookup_user_key_f(Key, User, SshDir, "authorized_keys", Opts) of - {ok, Key} -> - {ok, Key}; - _ -> - lookup_user_key_f(Key, User, SshDir, "authorized_keys2", Opts) + {ok, Fd} -> + ok = file:change_mode(KnownHosts, 8#644), + KeyType = erlang:atom_to_binary(ssh_transport:public_algo(Key), latin1), + EncKey = ssh_message:ssh2_pubkey_encode(Key), + SshBin = + iolist_to_binary([Host1, " ", + KeyType," ",base64:encode(iolist_to_binary(EncKey)), + "\n"]), + Res = file:write(Fd, SshBin), + file:close(Fd), + Res; + Error -> + Error end. +%%%================================================================ +%%% +%%% Local functions +%%% -%% -%% Utils -%% +%%%---------------- SERVER FUNCTIONS ------------------------------ -%% server use this to find individual keys for -%% an individual user when user tries to login -%% with publickey -ssh_dir({remoteuser, User}, Opts) -> - case proplists:get_value(user_dir_fun, Opts) of - undefined -> - case proplists:get_value(user_dir, Opts, false) of - false -> - default_user_dir(); - Dir -> - Dir - end; - FUN -> - FUN(User) - end; +lookup_auth_keys(KeyType, Key, File) -> + case file:read_file(File) of + {ok,Bin} -> + Lines = binary:split(Bin, <<"\n">>, [global,trim_all]), + find_key(KeyType, Key, Lines); + _ -> + false + end. -%% client use this to find client ssh keys -ssh_dir(user, Opts) -> - case proplists:get_value(user_dir, Opts, false) of - false -> default_user_dir(); - D -> D +find_key(KeyType, Key, [Line|Lines]) -> + case find_key_in_line(KeyType, Key, binary:split(Line, <<" ">>, [global,trim_all])) of + true -> + true; + false -> + find_key(KeyType, Key, Lines) end; +find_key(_, _, _) -> + false. -%% server use this to find server host keys -ssh_dir(system, Opts) -> - proplists:get_value(system_dir, Opts, "/etc/ssh"). - - -file_name(Type, Name, Opts) -> - FN = filename:join(ssh_dir(Type, Opts), Name), - FN. + +find_key_in_line(_KeyType, _Key, [<<"#",_/binary>> |_]) -> + false; +find_key_in_line(KeyType, Key, [KeyType, Base64EncodedKey, _Comment]) -> + %% Right KeyType. Try to decode to see if it matches + Key == decode_key(Base64EncodedKey); +find_key_in_line(KeyType, Key, [_Option | [_,_,_|_]=Rest]) -> + %% Dont care for options + find_key_in_line(KeyType, Key, Rest); +find_key_in_line(_, _, _) -> + false. +decode_key(Base64EncodedKey) -> + ssh_message:ssh2_pubkey_decode( + base64:mime_decode(Base64EncodedKey)). +%%%---------------- CLIENT FUNCTIONS ------------------------------ +%%%-------------------------------- %% in: "host" out: "host,1.2.3.4. add_ip(IP) when is_tuple(IP) -> ssh_connection:encode_ip(IP); -add_ip(Host) -> +add_ip(Host) -> case inet:getaddr(Host, inet) of {ok, Addr} -> case ssh_connection:encode_ip(Addr) of false -> Host; + Host -> Host; IPString -> Host ++ "," ++ IPString end; _ -> Host - end. + end. replace_localhost("localhost") -> {ok, Hostname} = inet:gethostname(), @@ -243,142 +165,203 @@ replace_localhost("localhost") -> replace_localhost(Host) -> Host. -do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> - case file:open(file_name(user, "known_hosts", Opts), [read, binary]) of - {ok, Fd} -> - Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg), - file:close(Fd), - Res; - {error, enoent} -> - {error, not_found}; - Error -> - Error - end. - -identity_key_filename('ssh-dss' ) -> "id_dsa"; -identity_key_filename('ssh-rsa' ) -> "id_rsa"; -identity_key_filename('rsa-sha2-256' ) -> "id_rsa"; -identity_key_filename('rsa-sha2-384' ) -> "id_rsa"; -identity_key_filename('rsa-sha2-512' ) -> "id_rsa"; -identity_key_filename('ssh-ed25519' ) -> "id_ed25519"; -identity_key_filename('ssh-ed448' ) -> "id_ed448"; -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("rsa-sha2-256" ) -> rsa_pass_phrase; -identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase; -identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase; -%% Not yet implemented: identity_pass_phrase("ssh-ed25519" ) -> ed25519_pass_phrase; -%% Not yet implemented: identity_pass_phrase("ssh-ed448" ) -> ed448_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)); -identity_pass_phrase(_) -> undefined. - -lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) -> - case io:get_line(Fd, '') of - eof -> - {error, not_found}; - {error,Error} -> - %% Rare... For example NFS errors - {error,Error}; - Line -> - case ssh_decode_line(Line, known_hosts) of - [{Key, Attributes}] -> - handle_host(Fd, KeyToMatch, Host, proplists:get_value(hostnames, Attributes), Key, KeyType); - [] -> - lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) - end +%%%-------------------------------- +lookup_host_keys(Hosts, KeyType, Key, File) -> + case file:read_file(File) of + {ok,Bin} -> + Lines = binary:split(Bin, <<"\n">>, [global,trim_all]), + find_key(Hosts, KeyType, Key, Lines); + _ -> + false end. -ssh_decode_line(Line, Type) -> - try - public_key:ssh_decode(Line, Type) - catch _:_ -> - [] - end. +find_key(Hosts, KeyType, Key, [Line|Lines]) -> + case find_key_in_line(Hosts, KeyType, Key, binary:split(Line, <<" ">>, [global,trim_all])) of + true -> + true; + false -> + find_key(Hosts, KeyType, Key, Lines) + end; +find_key(_, _, _, _) -> + false. -handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) -> - Host1 = host_name(Host), - case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of - true when KeyToMatch == Key -> - {ok,Key}; - _ -> - lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) - end. -host_name(Atom) when is_atom(Atom) -> - atom_to_list(Atom); -host_name(List) -> - List. - -key_match(#'RSAPublicKey'{}, 'ssh-rsa') -> - true; -key_match({_, #'Dss-Parms'{}}, 'ssh-dss') -> - true; -key_match({#'ECPoint'{},{namedCurve,Curve}}, Alg) -> - case atom_to_list(Alg) of - "ecdsa-sha2-"++IdS -> - Curve == public_key:ssh_curvename2oid(list_to_binary(IdS)); - _ -> - false - end; -key_match({ed_pub,ed25519,_}, 'ssh-ed25519') -> - true; -key_match({ed_pub,ed448,_}, 'ssh-ed448') -> - true; -key_match(_, _) -> +find_key_in_line(_Hosts, _KeyType, _Key, [<<"#",_/binary>> |_]) -> + false; +find_key_in_line(Hosts, KeyType, Key, [Patterns, KeyType, Base64EncodedKey, _Comment]) -> + host_match(Hosts, Patterns) andalso + Key == decode_key(Base64EncodedKey); +find_key_in_line(Hosts, KeyType, Key, [Patterns, KeyType, Base64EncodedKey]) -> + host_match(Hosts, Patterns) andalso + Key == decode_key(Base64EncodedKey); +find_key_in_line(Hosts, KeyType, Key, [_Option | [_,_,_|_]=Rest]) -> + %% Dont care for options + find_key_in_line(Hosts, KeyType, Key, Rest); +find_key_in_line(_, _, _, _) -> false. -add_key_fd(Fd, Host,Key) -> - SshBin = public_key:ssh_encode([{Key, [{hostnames, [Host]}]}], known_hosts), - file:write(Fd, SshBin). - -lookup_user_key_f(_, _User, [], _F, _Opts) -> - {error, nouserdir}; -lookup_user_key_f(_, _User, nouserdir, _F, _Opts) -> - {error, nouserdir}; -lookup_user_key_f(Key, _User, Dir, F, _Opts) -> - FileName = filename:join(Dir, F), - case file:open(FileName, [read, binary]) of - {ok, Fd} -> - Res = lookup_user_key_fd(Fd, Key), - file:close(Fd), - Res; - {error, Reason} -> - {error, {{openerr, Reason}, {file, FileName}}} + +host_match(Hosts, PatternsBin) -> + Patterns = binary:split(PatternsBin, <<",">>, [global]), + lists:any(fun(Pat) -> + lists:any(fun(Hst) -> + Pat == Hst + end, Hosts) + end, Patterns). + +%%%---------------- COMMON FUNCTIONS ------------------------------ + +read_ssh_key_file(Role, PrivPub, Algorithm, Opts) -> + File = file_name(Role, file_base_name(Role,Algorithm), Opts), + Password = %% Pwd for Host Keys is an undocumented option and should not be used + proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore), + + case file:read_file(File) of + {ok, Pem} -> + try + decode_ssh_file(PrivPub, Algorithm, Pem, Password) + catch + throw:Reason -> + {error, Reason}; + error:Reason -> + {error, Reason} + end; + + {error, Reason} -> + {error, Reason} end. -lookup_user_key_fd(Fd, Key) -> - case io:get_line(Fd, '') of - eof -> - {error, not_found}; - {error,Error} -> - %% Rare... For example NFS errors - {error,Error}; - Line -> - case ssh_decode_line(Line, auth_keys) of - [{AuthKey, _}] -> - case is_auth_key(Key, AuthKey) of - true -> - {ok, Key}; - false -> - lookup_user_key_fd(Fd, Key) - end; - [] -> - lookup_user_key_fd(Fd, Key) - end + +decode_ssh_file(PrivPub, Algorithm, Pem, Password) -> + %% Private Key + try get_key_part(Pem) of + {'openssh-key-v1', Bin, _KeyValues} -> + %% Holds both public and private keys + KeyPairs = new_openssh_decode(Bin, Password), + ValidKeys = + [Key || {Pub,Priv} <- KeyPairs, + Key <- [Pub,Priv], + ssh_transport:valid_key_sha_alg(PrivPub, Key, Algorithm)], + %% Select one (for now, just pick the first found): + case ValidKeys of + [Key|_] -> {ok,Key}; + [] -> {error,bad_keytype_in_file} + end; + + {rfc4716, Bin, _KeyValues} -> + %% rfc4716 only defines public keys + Key = ssh_message:ssh2_pubkey_decode(Bin), + case ssh_transport:valid_key_sha_alg(PrivPub, Key, Algorithm) of + true -> {ok,Key}; + false -> {error,bad_keytype_in_file} + end; + + {Type, Bin, KeyValues} -> + Key = + case get_encrypt_hdrs(KeyValues) of + not_encrypted -> + public_key:pem_entry_decode({Type,Bin,not_encrypted}); + [Cipher,Salt] when is_binary(Cipher), + is_binary(Salt), + Password =/= ignore -> + CryptInfo = + {binary_to_list(Cipher), unhex(binary_to_list(Salt))}, + public_key:pem_entry_decode({Type,Bin,CryptInfo}, Password); + _X -> + throw("No pass phrase provided for private key file") + end, + case ssh_transport:valid_key_sha_alg(PrivPub, Key, Algorithm) of + true -> {ok,Key}; + false -> {error,bad_keytype_in_file} + end + catch + _:_ -> error(bad_or_unsupported_key_format) end. -is_auth_key(Key, Key) -> - true; -is_auth_key(_,_) -> - false. +get_encrypt_hdrs(KVs) -> + lists:foldl(fun({<<"Proc-Type">>, <<"4,ENCRYPTED", _/binary>>}, _Acc) -> + {proc_type, <<"4,ENCRYPTED">>}; + ({<<"DEK-Info">>, DEKinfo}, {proc_type,_}) -> + binary:split(DEKinfo, <<",">>); + (_, Acc) -> + Acc + end, not_encrypted, KVs). + +unhex(S) -> + %% I would like to do erlang:list_to_integer(S,16), but that does not fit + %% the public_key:pem_entry_decode API + list_to_binary( + lists:foldr(fun(D2, {D1,Acc}) -> + [erlang:list_to_integer([D2,D1], 16) | Acc]; % sic! + (D1, Acc) when is_list(Acc) -> + {D1,Acc} + end, [], S)). + +file_base_name(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa"; +file_base_name(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa"; +file_base_name(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa"; +file_base_name(user, 'rsa-sha2-256' ) -> "id_rsa"; +file_base_name(user, 'rsa-sha2-384' ) -> "id_rsa"; +file_base_name(user, 'rsa-sha2-512' ) -> "id_rsa"; +file_base_name(user, 'ssh-dss' ) -> "id_dsa"; +file_base_name(user, 'ssh-ed25519' ) -> "id_ed25519"; +file_base_name(user, 'ssh-ed448' ) -> "id_ed448"; +file_base_name(user, 'ssh-rsa' ) -> "id_rsa"; +file_base_name(system, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; +file_base_name(system, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; +file_base_name(system, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key"; +file_base_name(system, 'rsa-sha2-256' ) -> "ssh_host_rsa_key"; +file_base_name(system, 'rsa-sha2-384' ) -> "ssh_host_rsa_key"; +file_base_name(system, 'rsa-sha2-512' ) -> "ssh_host_rsa_key"; +file_base_name(system, 'ssh-dss' ) -> "ssh_host_dsa_key"; +file_base_name(system, 'ssh-ed25519' ) -> "ssh_host_ed25519_key"; +file_base_name(system, 'ssh-ed448' ) -> "ssh_host_ed448_key"; +file_base_name(system, 'ssh-rsa' ) -> "ssh_host_rsa_key"; +file_base_name(system, _ ) -> "ssh_host_key". + + +identity_pass_phrase('ssh-dss' ) -> dsa_pass_phrase; +identity_pass_phrase('ssh-rsa' ) -> rsa_pass_phrase; +identity_pass_phrase('rsa-sha2-256' ) -> rsa_pass_phrase; +identity_pass_phrase('rsa-sha2-384' ) -> rsa_pass_phrase; +identity_pass_phrase('rsa-sha2-512' ) -> rsa_pass_phrase; +identity_pass_phrase('ecdsa-sha2-nistp256') -> ecdsa_pass_phrase; +identity_pass_phrase('ecdsa-sha2-nistp384') -> ecdsa_pass_phrase; +identity_pass_phrase('ecdsa-sha2-nistp521') -> ecdsa_pass_phrase; +%% Not yet implemented: identity_pass_phrase('ssh-ed25519' ) -> ed25519_pass_phrase; +%% Not yet implemented: identity_pass_phrase('ssh-ed448' ) -> ed448_pass_phrase; +identity_pass_phrase(_) -> undefined. + +%%%---------------------------------------------------------------- +file_name(Type, Name, Opts) -> + filename:join(ssh_dir(Type, Opts), Name). + + +%%%-------------------------------- +ssh_dir({remoteuser, User}, Opts) -> + %% server use this to find individual keys for an individual + %% user when user tries to login with publickey + case proplists:get_value(user_dir_fun, Opts) of + undefined -> + %% Try the local user instead + ssh_dir(user, Opts); + FUN -> + FUN(User) + end; +ssh_dir(user, Opts) -> + %% client use this to find client ssh keys + case proplists:get_value(user_dir, Opts, false) of + false -> default_user_dir(); + D -> D + end; + +ssh_dir(system, Opts) -> + %% server use this to find server host keys + proplists:get_value(system_dir, Opts, "/etc/ssh"). + +%%%-------------------------------- default_user_dir() -> try default_user_dir(os:getenv("HOME")) @@ -395,9 +378,138 @@ default_user_dir(Home) when is_list(Home) -> {ok,Info} = file:read_file_info(UserDir), #file_info{mode=Mode} = Info, case (Mode band 8#777) of - ?PERM_700 -> + 8#700 -> ok; _Other -> - ok = file:change_mode(UserDir, ?PERM_700) + ok = file:change_mode(UserDir, 8#700) end, UserDir. + +%%%################################################################ +get_key_part(RawBin) when is_binary(RawBin) -> + case binary:split( + binary:replace(RawBin, <<"\\\n">>, <<"">>, [global]), + <<"\n">>, [global,trim_all]) + of + [<<"---- BEGIN SSH2 PUBLIC KEY ----">> | Lines0] -> + %% RFC 4716 format + {KeyValues,Lines} = get_hdr_lines(Lines0, []), + ExpectedEndLine = <<"---- END SSH2 PUBLIC KEY ----">>, + {rfc4716, get_body(Lines,ExpectedEndLine), KeyValues}; + + [<<"-----BEGIN ", Rest/binary>> | Lines0] -> + %% PEM format + ExpectedEndLine = <<"-----END ",Rest/binary>>, + [MiddlePart, <<>>] = binary:split(Rest, <<" KEY-----">>), + {KeyValues,Lines} = get_hdr_lines(Lines0, []), + {asn1_type(MiddlePart), get_body(Lines,ExpectedEndLine), KeyValues} + end. + + +get_hdr_lines(Lines, Acc) -> + Line1 = hd(Lines), + case binary:split(Line1, <<":">>) of + [Line1] -> + {lists:reverse(Acc), Lines}; + [Key,Value] -> + get_hdr_lines(tl(Lines), [{trim(Key),trim(Value)}|Acc]) + end. + + +get_body(Lines, ExpectedEndLine) -> + {KeyPart, [ExpectedEndLine]} = lists:split(length(Lines)-1, Lines), + base64:mime_decode(iolist_to_binary(KeyPart)). + +trim(<<" ",B/binary>>) -> trim(B); +trim(B) -> B. + +asn1_type(<<"RSA PRIVATE">>) -> 'RSAPrivateKey'; +asn1_type(<<"RSA PUBLIC">>) -> 'RSAPublicKey'; +asn1_type(<<"DSA PRIVATE">>) -> 'DSAPrivateKey'; +asn1_type(<<"EC PRIVATE">>) -> 'ECPrivateKey'; +asn1_type(<<"OPENSSH PRIVATE">>) -> 'openssh-key-v1'; +asn1_type(_) -> undefined. + +%%%================================================================ +%%% From https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key +%%% + +-define(NON_CRYPT_BLOCKSIZE, 8). + +new_openssh_decode(<<"openssh-key-v1",0, + ?DEC_BIN(CipherName, _L1), + ?DEC_BIN(KdfName, _L2), + ?DEC_BIN(KdfOptions, _L3), + ?UINT32(N), % number of keys + Rest/binary + >>, Pwd) -> + new_openssh_decode(Rest, N, Pwd, CipherName, KdfName, KdfOptions, N, []). + + +new_openssh_decode(<<?DEC_BIN(BinKey,_L1), Rest/binary>>, I, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAcc) when I>0 -> + PublicKey = ssh_message:ssh2_pubkey_decode(BinKey), + new_openssh_decode(Rest, I-1, Pwd, CipherName, KdfName, KdfOptions, N, [PublicKey|PubKeyAcc]); + +new_openssh_decode(<<?DEC_BIN(Encrypted,_L)>>, + 0, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAccRev) -> + PubKeys = lists:reverse(PubKeyAccRev), + try + Plain = decrypt_new_openssh(Encrypted, KdfName, KdfOptions, CipherName, Pwd), + new_openssh_decode_priv_keys(Plain, N, N, [], []) + of + {PrivKeys, _Comments} -> + lists:map(fun({ {ed_pub,A,Pub}, {ed_pri,A,Pub,Pri0} }) -> + Pri = binary:part(Pri0, {0,size(Pri0)-size(Pub)}), + {{ed_pub,A,Pub}, {ed_pri,A,Pub,Pri}}; + (Pair) -> + Pair + end, lists:zip(PubKeys, PrivKeys)) + catch + error:{decryption, DecryptError} -> + error({decryption, DecryptError}) + end. + + +new_openssh_decode_priv_keys(Bin, I, N, KeyAcc, CmntAcc) when I>0 -> + {PrivKey, <<?DEC_BIN(Comment,_Lc),Rest/binary>>} = ssh_message:ssh2_privkey_decode2(Bin), + new_openssh_decode_priv_keys(Rest, I-1, N, [PrivKey|KeyAcc], [Comment|CmntAcc]); +new_openssh_decode_priv_keys(_Padding, 0, _N, PrivKeyAccRev, CommentAccRev) -> + {lists:reverse(PrivKeyAccRev), + lists:reverse(CommentAccRev)}. + + +decrypt_new_openssh(Encrypted, <<"none">>, <<>>, _CipherName, _Pwd) -> + check_valid_decryption(Encrypted, ?NON_CRYPT_BLOCKSIZE); +decrypt_new_openssh(Encrypted, <<>>, <<>>, _CipherName, _Pwd) -> + check_valid_decryption(Encrypted, ?NON_CRYPT_BLOCKSIZE); +decrypt_new_openssh(_Encrypted, <<"bcrypt">>, <<?DEC_BIN(_Salt,_L),?UINT32(_Rounds)>>, _CipherName, _Pwd) -> + error({decryption, {not_supported,bcrypt}}); +decrypt_new_openssh(_Encrypted, KdfName, _KdfOpts, _CipherName, _Pwd) -> + error({decryption, {not_supported,KdfName}}). + + +check_valid_decryption(<<?UINT32(Checkint1),?UINT32(Checkint2),Plain/binary>>, BlockSize) when Checkint2==Checkint1 -> + case check_padding(Plain, BlockSize) of + true -> + Plain; + false -> + error({decryption,bad_padding}) + end; +check_valid_decryption(_, _) -> + error({decryption,bad_result}). + + +check_padding(Bin, BlockSize) -> + N = binary:last(Bin), + if + N < BlockSize -> + %% Check that Bin is <<...,1,2,...,N>> + Padding = binary:part(Bin, {byte_size(Bin),-N}), + ExpectedPadding = list_to_binary(lists:seq(1,N)), % <<1,2,...,N>> + Padding == ExpectedPadding; + true -> + true + end. + +%%%================================================================ +%%% diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl index 79cd95e422..91365205aa 100644 --- a/lib/ssh/src/ssh_info.erl +++ b/lib/ssh/src/ssh_info.erl @@ -79,8 +79,8 @@ print_clients() -> lists:map(fun print_client/1, supervisor:which_children(sshc_sup)) catch - C:E -> - io_lib:format('***print_clients FAILED: ~p:~p~n',[C,E]) + C:E:S -> + io_lib:format('***print_clients FAILED: ~p:~p,~n ~p~n',[C,E,S]) end. print_client({undefined,Pid,supervisor,[ssh_connection_handler]}) -> @@ -94,9 +94,9 @@ print_client({undefined,Pid,supervisor,[ssh_connection_handler]}) -> io_lib:format(?INDENT?INDENT?INDENT"No channels~n",[]) end]; -print_client(Other) -> - io_lib:format(" [[Other 1: ~p]]~n",[Other]). - +print_client({{client,ssh_system_sup,_,_,_},Pid,supervisor,[ssh_system_sup]}) when is_pid(Pid) -> + lists:map(fun print_system_sup/1, + supervisor:which_children(Pid)). %%%================================================================ print_servers() -> @@ -104,8 +104,8 @@ print_servers() -> lists:map(fun print_server/1, supervisor:which_children(sshd_sup)) catch - C:E -> - io_lib:format('***print_servers FAILED: ~p:~p~n',[C,E]) + C:E:S -> + io_lib:format('***print_servers FAILED: ~p:~p,~n ~p~n',[C,E,S]) end. @@ -140,22 +140,33 @@ print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, superv -print_channels({{server,ssh_server_channel_sup,_,_},Pid,supervisor,[ssh_server_channel_sup]}) when is_pid(Pid) -> +print_channels({{Role,ssh_channel_sup,_,_},Pid,supervisor,[ssh_channel_sup]}) when is_pid(Pid) -> + ChanBehaviour = + case Role of + server -> ssh_server_channel; + client -> ssh_client_channel + end, Children = supervisor:which_children(Pid), - ChannelPids = [P || {R,P,worker,[ssh_server_channel]} <- Children, + ChannelPids = [P || {R,P,worker,[Mod]} <- Children, + ChanBehaviour == Mod, is_pid(P), is_reference(R)], case ChannelPids of [] -> io_lib:format(?INDENT?INDENT"No channels~n",[]); [Ch1Pid|_] -> - {{ConnManager,_}, _Str} = ssh_server_channel:get_print_info(Ch1Pid), + {{ConnManager,_}, _Str} = ChanBehaviour:get_print_info(Ch1Pid), {{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager), [io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]), lists:map(fun print_ch/1, ChannelPids) ] end; -print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_sup]}) when is_pid(Pid) -> - []. % The supervisor of the connections socket owning process +print_channels({{_Role,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_sup]}) when is_pid(Pid) -> + []; % The supervisor of the connections socket owning process + +print_channels({Ref,Pid,supervisor,[ssh_tcpip_forward_acceptor_sup]}) when is_pid(Pid), + is_reference(Ref) -> + []. % The supervisor of the forward_acceptor process + print_ch(Pid) -> try diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 47cbec1513..81c2e00018 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -31,6 +31,9 @@ -include("ssh_transport.hrl"). -export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]). +-export([ssh2_pubkey_decode/1, + ssh2_pubkey_encode/1, + ssh2_privkey_decode2/1]). -behaviour(ssh_dbg). -export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]). @@ -46,6 +49,11 @@ ucl(B) -> -define(unicode_list(B), ucl(B)). +%%%================================================================ +%%% +%%% Encode/decode messages +%%% + encode(#ssh_msg_global_request{ name = Name, want_reply = Bool, @@ -242,7 +250,7 @@ encode(#ssh_msg_kexdh_reply{ f = F, h_sig = Signature }) -> - EncKey = public_key:ssh_encode(Key, ssh2_pubkey), + EncKey = ssh2_pubkey_encode(Key), EncSign = encode_signature(Key, SigAlg, Signature), <<?Ebyte(?SSH_MSG_KEXDH_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>; @@ -268,7 +276,7 @@ encode(#ssh_msg_kex_dh_gex_reply{ f = F, h_sig = Signature }) -> - EncKey = public_key:ssh_encode(Key, ssh2_pubkey), + EncKey = ssh2_pubkey_encode(Key), EncSign = encode_signature(Key, SigAlg, Signature), <<?Ebyte(?SSH_MSG_KEX_DH_GEX_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>; @@ -276,7 +284,7 @@ encode(#ssh_msg_kex_ecdh_init{q_c = 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), + EncKey = ssh2_pubkey_encode(Key), EncSign = encode_signature(Key, SigAlg, Sign), <<?Ebyte(?SSH_MSG_KEX_ECDH_REPLY), ?Ebinary(EncKey), ?Ebinary(Q_s), ?Ebinary(EncSign)>>; @@ -453,7 +461,7 @@ decode(<<"dh",?BYTE(?SSH_MSG_KEXDH_INIT), ?DEC_MPINT(E,__0)>>) -> decode(<<"dh", ?BYTE(?SSH_MSG_KEXDH_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) -> #ssh_msg_kexdh_reply{ - public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), + public_host_key = ssh2_pubkey_decode(Key), f = F, h_sig = decode_signature(Hashsign) }; @@ -483,7 +491,7 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_INIT), ?DEC_MPINT(E,__0)>>) -> decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) -> #ssh_msg_kex_dh_gex_reply{ - public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), + public_host_key = ssh2_pubkey_decode(Key), f = F, h_sig = decode_signature(Hashsign) }; @@ -496,7 +504,7 @@ decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_BIN(Q_c,__0)>>) -> decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY), ?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), + public_host_key = ssh2_pubkey_decode(Key), q_s = Q_s, h_sig = decode_signature(Sig) }; @@ -540,6 +548,132 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__ message = Msg, language = Lang}. + +%%%================================================================ +%%% +%%% Encode/decode ssh public/private keys +%%% + +%%%-------- public key -------- +ssh2_pubkey_encode(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> + <<?STRING(<<"ssh-rsa">>), ?Empint(E), ?Empint(N)>>; +ssh2_pubkey_encode({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) -> + <<?STRING(<<"ssh-dss">>), ?Empint(P), ?Empint(Q), ?Empint(G), ?Empint(Y)>>; +ssh2_pubkey_encode({#'ECPoint'{point = Q}, {namedCurve,OID}}) -> + Curve = public_key:oid2ssh_curvename(OID), + KeyType = <<"ecdsa-sha2-", Curve/binary>>, + <<?STRING(KeyType), ?STRING(Curve), ?Estring(Q)>>; +ssh2_pubkey_encode({ed_pub, ed25519, Key}) -> + <<?STRING(<<"ssh-ed25519">>), ?Estring(Key)>>; +ssh2_pubkey_encode({ed_pub, ed448, Key}) -> + <<?STRING(<<"ssh-ed448">>), ?Estring(Key)>>. + +%%%-------- +ssh2_pubkey_decode(KeyBlob) -> + {Key,_RestBlob} = ssh2_pubkey_decode2(KeyBlob), + Key. + +ssh2_pubkey_decode2(<<?UINT32(7), "ssh-rsa", + ?DEC_INT(E, _EL), + ?DEC_INT(N, _NL), + Rest/binary>>) -> + {#'RSAPublicKey'{modulus = N, + publicExponent = E + }, Rest}; +ssh2_pubkey_decode2(<<?UINT32(7), "ssh-dss", + ?DEC_INT(P, _PL), + ?DEC_INT(Q, _QL), + ?DEC_INT(G, _GL), + ?DEC_INT(Y, _YL), + Rest/binary>>) -> + {{Y, #'Dss-Parms'{p = P, + q = Q, + g = G} + }, Rest}; +ssh2_pubkey_decode2(<<?UINT32(TL), "ecdsa-sha2-",KeyRest/binary>>) -> + Sz = TL-11, + <<_Curve:Sz/binary, + ?DEC_BIN(SshName, _IL), + ?DEC_BIN(Q, _QL), + Rest/binary>> = KeyRest, + OID = public_key:ssh_curvename2oid(SshName), + {{#'ECPoint'{point = Q}, {namedCurve,OID} + }, Rest}; +ssh2_pubkey_decode2(<<?UINT32(11), "ssh-ed25519", + ?DEC_BIN(Key, _L), + Rest/binary>>) -> + {{ed_pub, ed25519, Key}, + Rest}; +ssh2_pubkey_decode2(<<?UINT32(9), "ssh-ed448", + ?DEC_BIN(Key, _L), + Rest/binary>>) -> + {{ed_pub, ed448, Key}, + Rest}. + +%%%-------- private key -------- + +%% dialyser... ssh2_privkey_decode(KeyBlob) -> +%% dialyser... {Key,_RestBlob} = ssh2_privkey_decode2(KeyBlob), +%% dialyser... Key. + +%% See sshkey_private_serialize_opt in sshkey.c +ssh2_privkey_decode2(<<?UINT32(7), "ssh-rsa", + ?DEC_INT(N, _NL), % Yes, N and E is reversed relative pubkey format + ?DEC_INT(E, _EL), % --"-- + ?DEC_INT(D, _DL), + ?DEC_INT(IQMP, _IQMPL), + ?DEC_INT(P, _PL), + ?DEC_INT(Q, _QL), + Rest/binary>>) -> + {#'RSAPrivateKey'{version = 'two-prime', % Found this in public_key:generate_key/1 .. + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + %exponent1, % D_mod_P_1 + %exponent2, % D_mod_Q_1 + coefficient = IQMP + }, Rest}; +ssh2_privkey_decode2(<<?UINT32(7), "ssh-dss", + ?DEC_INT(P, _PL), + ?DEC_INT(Q, _QL), + ?DEC_INT(G, _GL), + ?DEC_INT(Y, _YL), % Publ key + ?DEC_INT(X, _XL), % Priv key + Rest/binary>>) -> + {#'DSAPrivateKey'{version = 0, + p = P, + q = Q, + g = G, + y = Y, + x = X + }, Rest}; +ssh2_privkey_decode2(<<?UINT32(TL), "ecdsa-sha2-",KeyRest/binary>>) -> + Sz = TL-11, + <<_Curve:Sz/binary, + ?DEC_BIN(CurveName, _SNN), + ?DEC_BIN(Q, _QL), + ?DEC_BIN(Priv, _PrivL), + Rest/binary>> = KeyRest, + OID = public_key:ssh_curvename2oid(CurveName), + {#'ECPrivateKey'{version = 1, + parameters = {namedCurve,OID}, + privateKey = Priv, + publicKey = Q + }, Rest}; +ssh2_privkey_decode2(<<?UINT32(11), "ssh-ed25519", + ?DEC_BIN(Pub,_Lpub), + ?DEC_BIN(Priv,_Lpriv), + Rest/binary>>) -> + {{ed_pri, ed25519, Pub, Priv}, Rest}; +ssh2_privkey_decode2(<<?UINT32(9), "ssh-ed448", + ?DEC_BIN(Pub,_Lpub), + ?DEC_BIN(Priv,_Lpriv), + Rest/binary>>) -> + {{ed_pri, ed448, Pub, Priv}, Rest}. + + %%%================================================================ %%% %%% Helper functions @@ -594,8 +728,8 @@ encode_signature(#'RSAPublicKey'{}, SigAlg, Signature) -> encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) -> <<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>; encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) -> - CurveName = public_key:oid2ssh_curvename(OID), - <<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>; + Curve = public_key:oid2ssh_curvename(OID), + <<?Ebinary(<<"ecdsa-sha2-",Curve/binary>>), ?Ebinary(Signature)>>; encode_signature({ed_pub, ed25519,_}, _SigAlg, Signature) -> <<?Ebinary(<<"ssh-ed25519">>), ?Ebinary(Signature)>>; encode_signature({ed_pub, ed448,_}, _SigAlg, Signature) -> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index e2a18edc0d..b165c77341 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -331,6 +331,18 @@ default(server) -> class => user_option }, + tcpip_tunnel_out => + #{default => false, + chk => fun erlang:is_boolean/1, + class => user_option + }, + + tcpip_tunnel_in => + #{default => false, + chk => fun erlang:is_boolean/1, + class => user_option + }, + system_dir => #{default => "/etc/ssh", chk => fun(V) -> check_string(V) andalso check_dir(V) end, diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 11d48bb1e5..007f912e5f 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -165,8 +165,8 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> PacketSize = proplists:get_value(packet_size, ChanOpts, ?XFER_PACKET_SIZE), case ssh_connection:session_channel(Cm, WindowSize, PacketSize, Timeout) of {ok, ChannelId} -> - case ssh_client_channel:start(Cm, ChannelId, - ?MODULE, [Cm, ChannelId, SftpOpts]) of + case ssh_connection_handler:start_channel(Cm, ?MODULE, ChannelId, + [Cm,ChannelId,SftpOpts], undefined) of {ok, Pid} -> case wait_for_version_negotiation(Pid, Timeout) of ok -> @@ -175,9 +175,7 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> TimeOut end; {error, Reason} -> - {error, format_channel_start_error(Reason)}; - ignore -> - {error, ignore} + {error, format_channel_start_error(Reason)} end; Error -> Error diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index b8dc905d4d..d478d939ff 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -62,14 +62,15 @@ %%==================================================================== -spec subsystem_spec(Options) -> Spec when Options :: [ {cwd, string()} | - {file_handler, CallbackModule::string()} | + {file_handler, CbMod | {CbMod, FileState}} | {max_files, integer()} | {root, string()} | {sftpd_vsn, integer()} ], Spec :: {Name, {CbMod,Options}}, Name :: string(), - CbMod :: atom() . + CbMod :: atom(), + FileState :: term(). subsystem_spec(Options) -> diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl index 5fc8f7e764..140b219b32 100644 --- a/lib/ssh/src/ssh_subsystem_sup.erl +++ b/lib/ssh/src/ssh_subsystem_sup.erl @@ -30,7 +30,9 @@ -export([start_link/5, connection_supervisor/1, - channel_supervisor/1 + channel_supervisor/1, + tcpip_fwd_supervisor/1, + start_channel/8 ]). %% Supervisor callback @@ -46,9 +48,17 @@ connection_supervisor(SupPid) -> Children = supervisor:which_children(SupPid), ssh_connection_sup(Children). -channel_supervisor(SupPid) -> +channel_supervisor(SupPid) when is_pid(SupPid) -> Children = supervisor:which_children(SupPid), - ssh_server_channel_sup(Children). + ssh_channel_sup(Children). + +tcpip_fwd_supervisor(SupPid) when is_pid(SupPid) -> + Children = supervisor:which_children(SupPid), + tcpip_fwd_sup(Children). + +start_channel(Role, SupPid, ConnRef, Callback, Id, Args, Exec, Opts) -> + ChannelSup = channel_supervisor(SupPid), + ssh_channel_sup:start_child(Role, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts). %%%========================================================================= %%% Supervisor callback @@ -64,11 +74,10 @@ init([Role, Address, Port, Profile, Options]) -> %%%========================================================================= %%% Internal functions %%%========================================================================= -child_specs(client, _Address, _Port, _Profile, _Options) -> - []; -child_specs(server, Address, Port, Profile, Options) -> - [ssh_channel_child_spec(server, Address, Port, Profile, Options), - ssh_connection_child_spec(server, Address, Port, Profile, Options)]. +child_specs(Role, Address, Port, Profile, Options) -> + [ssh_channel_child_spec(Role, Address, Port, Profile, Options), + ssh_connection_child_spec(Role, Address, Port, Profile, Options), + ssh_tcpip_forward_acceptor_child_spec()]. ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> #{id => id(Role, ssh_connection_sup, Address, Port), @@ -78,12 +87,20 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) -> }. ssh_channel_child_spec(Role, Address, Port, _Profile, Options) -> - #{id => id(Role, ssh_server_channel_sup, Address, Port), - start => {ssh_server_channel_sup, start_link, [Options]}, + #{id => id(Role, ssh_channel_sup, Address, Port), + start => {ssh_channel_sup, start_link, [Options]}, + restart => temporary, + type => supervisor + }. + +ssh_tcpip_forward_acceptor_child_spec() -> + #{id => make_ref(), + start => {ssh_tcpip_forward_acceptor_sup, start_link, []}, restart => temporary, type => supervisor }. + id(Role, Sup, Address, Port) -> {Role, Sup, Address, Port}. @@ -92,10 +109,13 @@ ssh_connection_sup([{_, Child, _, [ssh_connection_sup]} | _]) -> ssh_connection_sup([_ | Rest]) -> ssh_connection_sup(Rest). -ssh_server_channel_sup([{_, Child, _, [ssh_server_channel_sup]} | _]) -> +ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) -> Child; -ssh_server_channel_sup([_ | Rest]) -> - ssh_server_channel_sup(Rest). - +ssh_channel_sup([_ | Rest]) -> + ssh_channel_sup(Rest). +tcpip_fwd_sup([{_, Child, _, [ssh_tcpip_forward_acceptor_sup]} | _]) -> + Child; +tcpip_fwd_sup([_ | Rest]) -> + tcpip_fwd_sup(Rest). diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 80406a6c47..704c17c4e7 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -31,9 +31,9 @@ -include("ssh.hrl"). --export([start_link/4, stop_listener/1, - stop_listener/3, stop_system/1, - stop_system/3, system_supervisor/3, +-export([start_link/5, stop_listener/1, + stop_listener/3, stop_system/2, + stop_system/4, system_supervisor/3, subsystem_supervisor/1, channel_supervisor/1, connection_supervisor/1, acceptor_supervisor/1, start_subsystem/6, @@ -50,14 +50,14 @@ %%%========================================================================= %%% API %%%========================================================================= -start_link(Address, Port, Profile, Options) -> +start_link(Role, Address, Port, Profile, Options) -> Name = make_name(Address, Port, Profile), - supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]). + supervisor:start_link({local, Name}, ?MODULE, [Role, Address, Port, Profile, Options]). %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init([Address, Port, Profile, Options]) -> +init([server, Address, Port, Profile, Options]) -> SupFlags = #{strategy => one_for_one, intensity => 0, period => 3600 @@ -73,6 +73,14 @@ init([Address, Port, Profile, Options]) -> _ -> [] end, + {ok, {SupFlags,ChildSpecs}}; + +init([client, _Address, _Port, _Profile, _Options]) -> + SupFlags = #{strategy => one_for_one, + intensity => 0, + period => 3600 + }, + ChildSpecs = [], {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= @@ -92,11 +100,10 @@ stop_listener(Address, Port, Profile) -> system_supervisor(Address, Port, Profile)). -stop_system(SysSup) -> - catch sshd_sup:stop_child(SysSup), - ok. +stop_system(server, SysSup) -> catch sshd_sup:stop_child(SysSup), ok; +stop_system(client, SysSup) -> catch sshc_sup:stop_child(SysSup), ok. -stop_system(Address, Port, Profile) -> +stop_system(server, Address, Port, Profile) -> catch sshd_sup:stop_child(Address, Port, Profile), ok. diff --git a/lib/ssh/src/ssh_tcpip_forward_acceptor.erl b/lib/ssh/src/ssh_tcpip_forward_acceptor.erl new file mode 100644 index 0000000000..6f2fda2bf9 --- /dev/null +++ b/lib/ssh/src/ssh_tcpip_forward_acceptor.erl @@ -0,0 +1,116 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: The supervisor for tcpip-forwarding acceptor +%%---------------------------------------------------------------------- + +-module(ssh_tcpip_forward_acceptor). + +-export([supervised_start/6, + start_link/6]). + +-include("ssh.hrl"). + +%%%---------------------------------------------------------------- +supervised_start(FwdSup, {ListenAddrStr, ListenPort}, ConnectToAddr, ChanType, ChanCB, ConnPid) -> + case get_fwd_listen_opts(ListenAddrStr) of + {ok,Opts} -> + %% start listening on Addr:BoundPort + case gen_tcp:listen(ListenPort, [binary, + {reuseaddr,true}, + {active,false} | Opts]) of + {ok,LSock} -> + {ok,{_, TrueListenPort}} = inet:sockname(LSock), + ssh_tcpip_forward_acceptor_sup:start_child(FwdSup, + LSock, + {ListenAddrStr,TrueListenPort}, + ConnectToAddr, + ChanType, + ChanCB, + ConnPid), + {ok, TrueListenPort}; + + {error,Error} -> + {error,Error} + end; + + {error,Error} -> + {error,Error} + end. + + +%%%---------------------------------------------------------------- +start_link(LSock, {ListenAddrStr,ListenPort}, ConnectToAddr, ChanType, ChanCB, ConnPid) -> + Pid = proc_lib:spawn_link( + fun() -> + acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid) + end), + {ok, Pid}. + +%%%================================================================ +%%% +%%% Internal +%%% +acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid) -> + case gen_tcp:accept(LSock) of + {ok, Sock} -> + {ok, {RemHost,RemPort}} = inet:peername(Sock), + RemHostBin = list_to_binary(encode_ip(RemHost)), + Data = + case ConnectToAddr of + undefined -> + <<?STRING(ListenAddrStr), ?UINT32(ListenPort), + ?STRING(RemHostBin), ?UINT32(RemPort)>>; + {ConnectToHost, ConnectToPort} -> + <<?STRING(ConnectToHost), ?UINT32(ConnectToPort), + ?STRING(RemHostBin), ?UINT32(RemPort)>> + end, + case ssh_connection:open_channel(ConnPid, ChanType, Data, infinity) of + {ok,ChId} -> + gen_tcp:controlling_process(Sock, ConnPid), + ConnPid ! {fwd_connect_received, Sock, ChId, ChanCB}; + _ -> + gen_tcp:close(Sock) + end, + acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid); + + {error,closed} -> + ok + end. + +%%%---------------------------------------------------------------- +get_fwd_listen_opts(<<"">> ) -> {ok, []}; +get_fwd_listen_opts(<<"0.0.0.0">> ) -> {ok, [inet]}; +get_fwd_listen_opts(<<"::">> ) -> {ok, [inet6]}; +get_fwd_listen_opts(<<"localhost">>) -> {ok, [{ip,loopback}]}; +get_fwd_listen_opts(AddrStr) -> + case inet:getaddr(binary_to_list(AddrStr), inet) of + {ok, Addr} -> {ok, [{ip,Addr}]}; + {error,Error} -> {error,Error} + end. + +%%%---------------------------------------------------------------- +encode_ip(Addr) when is_tuple(Addr) -> + case catch inet_parse:ntoa(Addr) of + {'EXIT',_} -> false; + A -> A + end. diff --git a/lib/ssh/src/ssh_server_channel_sup.erl b/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl index ff74061bb3..522ce650ff 100644 --- a/lib/ssh/src/ssh_server_channel_sup.erl +++ b/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl @@ -20,42 +20,42 @@ %% %%---------------------------------------------------------------------- -%% Purpose: Ssh channel supervisor. +%% Purpose: The supervisor for tcpip-forwarding acceptor %%---------------------------------------------------------------------- --module(ssh_server_channel_sup). +-module(ssh_tcpip_forward_acceptor_sup). -behaviour(supervisor). --export([start_link/1, start_child/5]). +-include("ssh.hrl"). + +-export([start_link/0, start_child/7]). %% Supervisor callback -export([init/1]). %%%========================================================================= -%%% Internal API +%%% API %%%========================================================================= -start_link(Args) -> - supervisor:start_link(?MODULE, [Args]). - -start_child(Sup, Callback, Id, Args, Exec) -> - ChildSpec = - #{id => make_ref(), - start => {ssh_server_channel, start_link, [self(), Id, Callback, Args, Exec]}, - restart => temporary, - type => worker, - modules => [ssh_server_channel] - }, - supervisor:start_child(Sup, ChildSpec). +start_link() -> + supervisor:start_link(?MODULE, []). + +start_child(Sup, LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid) -> + Args = [LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid], + supervisor:start_child(Sup, Args). %%%========================================================================= %%% Supervisor callback %%%========================================================================= -init(_Args) -> - RestartStrategy = one_for_one, - MaxR = 10, - MaxT = 3600, - Children = [], - {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. +init([]) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 10, + period => 3600 + }, + ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. + start => {ssh_tcpip_forward_acceptor, start_link, []} + } + ], + {ok, {SupFlags,ChildSpecs}}. %%%========================================================================= %%% Internal functions diff --git a/lib/ssh/src/ssh_tcpip_forward_client.erl b/lib/ssh/src/ssh_tcpip_forward_client.erl new file mode 100644 index 0000000000..490d283461 --- /dev/null +++ b/lib/ssh/src/ssh_tcpip_forward_client.erl @@ -0,0 +1,84 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssh_tcpip_forward_client). + +-behaviour(ssh_client_channel). + +-record(state, { + id, cm, + fwd_socket + }). + +-export([init/1, handle_call/3, handle_cast/2, handle_msg/2, handle_ssh_msg/2, terminate/2, code_change/3]). + +init([FwdSocket]) -> + {ok, #state{fwd_socket=FwdSocket}}. + + +handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) -> + {ok, State#state{id = ChannelId, + cm = ConnectionManager}}; + +handle_msg({tcp,Sock,Data}, #state{fwd_socket = Sock, + cm = CM, + id = ChId} = State) -> + ssh_connection:send(CM, ChId, Data), + inet:setopts(Sock, [{active,once}]), + {ok, State}; + +handle_msg({tcp_closed,Sock}, #state{fwd_socket = Sock, + cm = CM, + id = ChId} = State) -> + ssh_connection:send_eof(CM, ChId), + {stop, ChId, State#state{fwd_socket=undefined}}. + + + +handle_ssh_msg({ssh_cm, _CM, {data, _ChannelId, _Type, Data}}, #state{fwd_socket=Sock} = State) -> + gen_tcp:send(Sock, Data), + {ok, State}; + +handle_ssh_msg({ssh_cm, _CM, {eof, ChId}}, State) -> + {stop, ChId, State}; + +handle_ssh_msg({ssh_cm, _CM, {signal, _, _}}, State) -> + %% Ignore signals according to RFC 4254 section 6.9. + {ok, State}; + +handle_ssh_msg({ssh_cm, _CM, {exit_signal, ChId, _, _Error, _}}, State) -> + {stop, ChId, State}; + +handle_ssh_msg({ssh_cm, _, {exit_status, ChId, _Status}}, State) -> + {stop, ChId, State}. + + +terminate(_Reason, #state{fwd_socket=Sock}) -> + gen_tcp:close(Sock), + ok. + + +handle_call(Req, _, S) -> {reply, {unknown,Req}, S}. + +handle_cast(_, S) -> {noreply, S}. + +code_change(_, S, _) -> {ok, S}. + + diff --git a/lib/ssh/src/ssh_tcpip_forward_srv.erl b/lib/ssh/src/ssh_tcpip_forward_srv.erl new file mode 100644 index 0000000000..a29f70cf48 --- /dev/null +++ b/lib/ssh/src/ssh_tcpip_forward_srv.erl @@ -0,0 +1,75 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssh_tcpip_forward_srv). + +-behaviour(ssh_server_channel). + +-record(state, { + id, cm, + fwd_socket + }). + +-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]). + +init([FwdSocket]) -> + {ok, #state{fwd_socket=FwdSocket}}. + + +handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) -> + {ok, State#state{id = ChannelId, + cm = ConnectionManager}}; + +handle_msg({tcp,Sock,Data}, #state{fwd_socket = Sock, + cm = CM, + id = ChId} = State) -> + ssh_connection:send(CM, ChId, Data), + inet:setopts(Sock, [{active,once}]), + {ok, State}; + +handle_msg({tcp_closed,Sock}, #state{fwd_socket = Sock, + cm = CM, + id = ChId} = State) -> + ssh_connection:send_eof(CM, ChId), + {stop, ChId, State#state{fwd_socket=undefined}}. + + + +handle_ssh_msg({ssh_cm, _CM, {data, _ChannelId, _Type, Data}}, #state{fwd_socket=Sock} = State) -> + gen_tcp:send(Sock, Data), + {ok, State}; + +handle_ssh_msg({ssh_cm, _CM, {eof, ChId}}, State) -> + {stop, ChId, State}; + +handle_ssh_msg({ssh_cm, _CM, {signal, _, _}}, State) -> + %% Ignore signals according to RFC 4254 section 6.9. + {ok, State}; + +handle_ssh_msg({ssh_cm, _CM, {exit_signal, ChId, _, _Error, _}}, State) -> + {stop, ChId, State}; + +handle_ssh_msg({ssh_cm, _, {exit_status, ChId, _Status}}, State) -> + {stop, ChId, State}. + + +terminate(_Reason, #state{fwd_socket=Sock}) -> + gen_tcp:close(Sock), + ok. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 9a4ae5bbc6..e1a7ecf66f 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -50,10 +50,11 @@ parallell_gen_key/1, extract_public_key/1, ssh_packet/2, pack/2, - valid_key_sha_alg/2, + valid_key_sha_alg/3, sha/1, sign/3, verify/5, get_host_key/2, - call_KeyCb/3]). + call_KeyCb/3, + public_algo/1]). -behaviour(ssh_dbg). -export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]). @@ -786,7 +787,7 @@ get_host_key(SignAlg, Opts) -> case call_KeyCb(host_key, [SignAlg], Opts) of {ok, PrivHostKey} -> %% Check the key - the KeyCb may be a buggy plugin - case valid_key_sha_alg(PrivHostKey, SignAlg) of + case valid_key_sha_alg(private, PrivHostKey, SignAlg) of true -> PrivHostKey; false -> exit({error, bad_hostkey}) end; @@ -804,7 +805,7 @@ extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> 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}) -> + publicKey = Q}) when is_tuple(OID) -> {#'ECPoint'{point=Q}, {namedCurve,OID}}; extract_public_key({ed_pri, Alg, Pub, _Priv}) -> {ed_pub, Alg, Pub}; @@ -1480,7 +1481,7 @@ encrypt(#ssh{encrypt = 'chacha20-poly1305@openssh.com', %% MAC tag PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, true), EncBytes = <<EncLen/binary,EncPayloadData/binary>>, - Ctag = crypto:poly1305(PolyKey, EncBytes), + Ctag = crypto:mac(poly1305, PolyKey, EncBytes), %% Result {Ssh, {EncBytes,Ctag}}; @@ -1561,7 +1562,7 @@ decrypt(#ssh{decrypt = 'chacha20-poly1305@openssh.com', %% The length is already decrypted and used to divide the input %% Check the mac (important that it is timing-safe): PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, false), - case crypto:equal_const_time(Ctag, crypto:poly1305(PolyKey, <<AAD/binary,Ctext/binary>>)) of + case crypto:equal_const_time(Ctag, crypto:mac(poly1305, PolyKey, <<AAD/binary,Ctext/binary>>)) of true -> %% MAC is ok, decode IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, @@ -1722,17 +1723,17 @@ recv_mac_final(SSH) -> mac(none, _ , _, _) -> <<>>; mac('hmac-sha1', Key, SeqNum, Data) -> - crypto:hmac(sha, Key, [<<?UINT32(SeqNum)>>, Data]); + crypto:mac(hmac, sha, Key, [<<?UINT32(SeqNum)>>, Data]); mac('hmac-sha1-96', Key, SeqNum, Data) -> - crypto:hmac(sha, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-sha1-96')); + crypto:macN(hmac, sha, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-sha1-96')); mac('hmac-md5', Key, SeqNum, Data) -> - crypto:hmac(md5, Key, [<<?UINT32(SeqNum)>>, Data]); + crypto:mac(hmac, md5, Key, [<<?UINT32(SeqNum)>>, Data]); mac('hmac-md5-96', Key, SeqNum, Data) -> - crypto:hmac(md5, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-md5-96')); + crypto:macN(hmac, md5, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-md5-96')); mac('hmac-sha2-256', Key, SeqNum, Data) -> - crypto:hmac(sha256, Key, [<<?UINT32(SeqNum)>>, Data]); + crypto:mac(hmac, sha256, Key, [<<?UINT32(SeqNum)>>, Data]); mac('hmac-sha2-512', Key, SeqNum, Data) -> - crypto:hmac(sha512, Key, [<<?UINT32(SeqNum)>>, Data]). + crypto:mac(hmac, sha512, Key, [<<?UINT32(SeqNum)>>, Data]). %%%---------------------------------------------------------------- @@ -1760,7 +1761,7 @@ kex_hash(SSH, Key, HashAlg, Args) -> kex_plaintext(SSH, Key, Args) -> - EncodedKey = public_key:ssh_encode(Key, ssh2_pubkey), + EncodedKey = ssh_message:ssh2_pubkey_encode(Key), <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version), ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(EncodedKey), @@ -1787,33 +1788,36 @@ 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(_, #{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; -valid_key_sha_alg(#'RSAPublicKey'{}, 'ssh-rsa' ) -> true; +valid_key_sha_alg(public, #'RSAPublicKey'{}, 'rsa-sha2-512') -> true; +valid_key_sha_alg(public, #'RSAPublicKey'{}, 'rsa-sha2-384') -> true; +valid_key_sha_alg(public, #'RSAPublicKey'{}, 'rsa-sha2-256') -> true; +valid_key_sha_alg(public, #'RSAPublicKey'{}, 'ssh-rsa' ) -> true; -valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-512') -> true; -valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-384') -> true; -valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-256') -> true; -valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true; +valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'rsa-sha2-512') -> true; +valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'rsa-sha2-384') -> true; +valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'rsa-sha2-256') -> true; +valid_key_sha_alg(private, #'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(public, {_, #'Dss-Parms'{}}, 'ssh-dss') -> true; +valid_key_sha_alg(private, #'DSAPrivateKey'{}, 'ssh-dss') -> true; -valid_key_sha_alg({ed_pub, ed25519,_}, 'ssh-ed25519') -> true; -valid_key_sha_alg({ed_pri, ed25519,_,_},'ssh-ed25519') -> true; -valid_key_sha_alg({ed_pub, ed448,_}, 'ssh-ed448') -> true; -valid_key_sha_alg({ed_pri, ed448,_,_}, 'ssh-ed448') -> true; +valid_key_sha_alg(public, {ed_pub, ed25519,_}, 'ssh-ed25519') -> true; +valid_key_sha_alg(private, {ed_pri, ed25519,_,_},'ssh-ed25519') -> true; +valid_key_sha_alg(public, {ed_pub, ed448,_}, 'ssh-ed448') -> true; +valid_key_sha_alg(private, {ed_pri, ed448,_,_}, 'ssh-ed448') -> true; -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(public, {#'ECPoint'{},{namedCurve,OID}}, Alg) when is_tuple(OID) -> + valid_key_sha_alg_ec(OID, Alg); +valid_key_sha_alg(private, #'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) when is_tuple(OID) -> + 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), - try Alg == list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve)) +valid_key_sha_alg_ec(OID, Alg) -> + try + Curve = public_key:oid2ssh_curvename(OID), + Alg == list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve)) catch _:_ -> false end. @@ -1825,9 +1829,9 @@ public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-cu public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss'; public_algo({ed_pub, ed25519,_}) -> 'ssh-ed25519'; public_algo({ed_pub, ed448,_}) -> 'ssh-ed448'; -public_algo({#'ECPoint'{},{namedCurve,OID}}) -> - Curve = public_key:oid2ssh_curvename(OID), - try list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve)) +public_algo({#'ECPoint'{},{namedCurve,OID}}) when is_tuple(OID) -> + SshName = public_key:oid2ssh_curvename(OID), + try list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(SshName)) catch _:_ -> undefined end. diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index 2292beaf68..d89b59100f 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -117,13 +117,20 @@ rename(XF, ReqID, OldPath, NewPath, Flags) -> true -> (<<>>) end, - xf_request(XF, ?SSH_FXP_RENAME, - [?uint32(ReqID), - ?string_utf8(OldPath), - ?string_utf8(NewPath), - FlagBits]). - - + Ext = XF#ssh_xfer.ext, + ExtRename = "posix-rename@openssh.com", + case lists:member({ExtRename, "1"}, Ext) of + true -> + extended(XF, ReqID, ExtRename, + [?string_utf8(OldPath), + ?string_utf8(NewPath)]); + false -> + xf_request(XF, ?SSH_FXP_RENAME, + [?uint32(ReqID), + ?string_utf8(OldPath), + ?string_utf8(NewPath), + FlagBits]) + end. %% Create directory mkdir(XF, ReqID, Path, Attrs) -> @@ -222,7 +229,7 @@ extended(XF, ReqID, Request, Data) -> xf_request(XF, ?SSH_FXP_EXTENDED, [?uint32(ReqID), ?string(Request), - ?binary(Data)]). + Data]). %% Send xfer request to connection manager diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl index 869de244ac..0c9f8be5a9 100644 --- a/lib/ssh/src/sshc_sup.erl +++ b/lib/ssh/src/sshc_sup.erl @@ -27,7 +27,10 @@ -behaviour(supervisor). --export([start_link/0, start_child/1]). +-export([start_link/0, + start_child/4, + stop_child/1 + ]). %% Supervisor callback -export([init/1]). @@ -38,22 +41,45 @@ %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local,?SSHC_SUP}, ?MODULE, []). + supervisor:start_link({local,?MODULE}, ?MODULE, []). -start_child(Args) -> - supervisor:start_child(?MODULE, Args). +start_child(Address, Port, Profile, Options) -> + %% Here we a new connction on a new Host/EFERMERAL Port/Profile + Spec = child_spec(Address, Port, Profile, Options), + supervisor:start_child(?MODULE, Spec). + +stop_child(ChildId) when is_tuple(ChildId) -> + supervisor:terminate_child(?SSHC_SUP, ChildId); +stop_child(ChildPid) when is_pid(ChildPid)-> + stop_child(system_name(ChildPid)). %%%========================================================================= %%% Supervisor callback %%%========================================================================= init(_) -> - SupFlags = #{strategy => simple_one_for_one, + SupFlags = #{strategy => one_for_one, intensity => 0, period => 3600 }, - ChildSpecs = [#{id => undefined, % As simple_one_for_one is used. - start => {ssh_connection_handler, start_link, []}, - restart => temporary % because there is no way to restart a crashed connection - } - ], + ChildSpecs = [], {ok, {SupFlags,ChildSpecs}}. + +%%%========================================================================= +%%% Internal functions +%%%========================================================================= +child_spec(Address, Port, Profile, Options) -> + #{id => id(Address, Port, Profile), + start => {ssh_system_sup, start_link, [client, Address, Port, Profile, Options]}, + restart => temporary, + type => supervisor + }. + +id(Address, Port, Profile) -> + {client, ssh_system_sup, Address, Port, Profile}. + +system_name(SysSup) -> + case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHC_SUP)) of + {Name, SysSup, _, _} -> Name; + false -> undefind + end. + diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl index b5361abba5..b716b66ec7 100644 --- a/lib/ssh/src/sshd_sup.erl +++ b/lib/ssh/src/sshd_sup.erl @@ -89,7 +89,7 @@ init(_) -> %%%========================================================================= child_spec(Address, Port, Profile, Options) -> #{id => id(Address, Port, Profile), - start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]}, + start => {ssh_system_sup, start_link, [server, Address, Port, Profile, Options]}, restart => temporary, type => supervisor }. diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile index e221e94075..ee8480dfdb 100644 --- a/lib/ssh/test/Makefile +++ b/lib/ssh/test/Makefile @@ -43,6 +43,7 @@ MODULES= \ ssh_engine_SUITE \ ssh_protocol_SUITE \ ssh_property_test_SUITE \ + ssh_pubkey_SUITE \ ssh_sftp_SUITE \ ssh_sftpd_SUITE \ ssh_sftpd_erlclient_SUITE \ diff --git a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl index 6470fa5f3d..cfcc56d1a4 100644 --- a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl +++ b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl @@ -186,7 +186,7 @@ gen_pubkey_string(Type) -> ecdsa -> {#'ECPoint'{point=[1,2,3,4,5]}, {namedCurve,{1,2,840,10045,3,1,7}}} % 'secp256r1' nistp256 end, - gen_string(public_key:ssh_encode(PubKey, ssh2_pubkey)). + gen_string(ssh_message:ssh2_pubkey_encode(PubKey)). gen_signature_string(Type) -> diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 2ed3a248d5..3a90b4d0fe 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -52,7 +52,8 @@ all() -> groups() -> [{all_tests, [?PARALLEL], [{group, ssh_renegotiate_SUITE}, - {group, ssh_basic_SUITE} + {group, ssh_basic_SUITE}, + ssh_file_is_host_key4 ]}, {ssh_basic_SUITE, [], [app_test, appup_test, @@ -900,13 +901,87 @@ known_hosts(Config) when is_list(Config) -> {ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity), ok = ssh:close(ConnectionRef), {ok, Binary} = file:read_file(KnownHosts), + ct:log("known_hosts:~n~p",[Binary]), Lines = string:tokens(binary_to_list(Binary), "\n"), [Line] = Lines, [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "), - [StoredHost, _Ip] = string:tokens(HostAndIp, ","), + [StoredHost|_] = string:tokens(HostAndIp, ","), true = ssh_test_lib:match_ip(StoredHost, Host), "ssh-" ++ _ = Alg, + NLines = length(binary:split(Binary, <<"\n">>, [global,trim_all])), + ct:log("NLines = ~p~n~p", [NLines,Binary]), + if + NLines>1 -> ct:fail("wrong num lines", []); + NLines<1 -> ct:fail("wrong num lines", []); + true -> ok + end, + + _ConnectionRef2 = + ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir}, + {user_interaction, false}, + silently_accept_hosts]), + {ok, Binary2} = file:read_file(KnownHosts), + case Binary of + Binary2 -> ok; + _ -> ct:log("2nd differ~n~p", [Binary2]), + ct:fail("wrong num lines", []) + end, + + Binary3 = <<"localhost,",Binary/binary>>, + ok = file:write_file(KnownHosts, Binary3), + _ConnectionRef3 = + ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir}, + {user_interaction, false}, + silently_accept_hosts]), + ct:log("New known_hosts:~n~p",[Binary3]), + {ok, Binary4} = file:read_file(KnownHosts), + case Binary3 of + Binary4 -> ok; + _ -> ct:log("2nd differ~n~p", [Binary4]), + ct:fail("wrong num lines", []) + end, + + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- +ssh_file_is_host_key4(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + KnownHosts = filename:join(PrivDir, "known_hosts"), + + Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29, + 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>}, + Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29, + 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241, + 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26, + 190,175,232,37,97,128>>}, + + FileContents = <<"h11,h12,h13,h14 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n", + "h21,[h22]:2345,h23 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh" + "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n" + >>, + ok = file:write_file(KnownHosts, FileContents), + + true = ssh_file:is_host_key(Key1, "h11", 'ssh-ed25519', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key1, "h12", 'ssh-ed25519', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key1, "h13", 'ssh-ed25519', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key1, "h14", 'ssh-ed25519', [{user_dir,PrivDir}]), + + true = ssh_file:is_host_key(Key1, "h11,noh1", 'ssh-ed25519', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key1, "noh1,h11", 'ssh-ed25519', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key1, "noh1,h12,noh2", 'ssh-ed25519', [{user_dir,PrivDir}]), + + true = ssh_file:is_host_key(Key2, "h21", 'ssh-ed448', [{user_dir,PrivDir}]), + false = ssh_file:is_host_key(Key2, "h22", 'ssh-ed448', [{user_dir,PrivDir}]), + false = ssh_file:is_host_key(Key2, "[h22]",'ssh-ed448', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key2 , "[h22]:2345",'ssh-ed448', [{user_dir,PrivDir}]), + true = ssh_file:is_host_key(Key2, "h23", 'ssh-ed448', [{user_dir,PrivDir}]), + + false = ssh_file:is_host_key(Key2, "h11", 'ssh-ed448', [{user_dir,PrivDir}]), + false = ssh_file:is_host_key(Key1, "h21", 'ssh-ed25519', [{user_dir,PrivDir}]), + + ok. + %%-------------------------------------------------------------------- %%% Test that we can use keyes protected by pass phrases diff --git a/lib/ssh/test/ssh_pubkey_SUITE.erl b/lib/ssh/test/ssh_pubkey_SUITE.erl new file mode 100644 index 0000000000..160a78beb2 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE.erl @@ -0,0 +1,269 @@ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssh_pubkey_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include("ssh_test_lib.hrl"). + +%%%---------------------------------------------------------------- +%%% Common Test interface functions ------------------------------- +%%%---------------------------------------------------------------- + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{seconds,40}}]. + +all() -> + [{group, old_format}, + {group, new_format} + ]. + + +-define(tests_old, [connect_rsa_to_rsa, + connect_rsa_to_dsa, + connect_rsa_to_ecdsa, + connect_dsa_to_rsa, + connect_dsa_to_dsa, + connect_dsa_to_ecdsa, + connect_ecdsa_to_rsa, + connect_ecdsa_to_dsa, + connect_ecdsa_to_ecdsa + ]). + +-define(tests_new, [connect_ecdsa_to_ed25519, + connect_rsa_to_ed25519, + connect_dsa_to_ed25519, + connect_ed25519_to_rsa, + connect_ed25519_to_dsa, + connect_ed25519_to_ecdsa, + connect_ed25519_to_ed25519 + | ?tests_old]). + +groups() -> + [{new_format, [], ?tests_new}, + {old_format, [], ?tests_old++[{group,passphrase}]}, + {passphrase, [], ?tests_old} + ]. + +%%%---------------------------------------------------------------- +init_per_suite(Config) -> + ?CHECK_CRYPTO( + begin + ssh:start(), + [{client_opts,[]} + | Config] + end). + +end_per_suite(_onfig) -> + ssh:stop(). + +%%%---------------------------------------------------------------- +init_per_group(new_format, Config) -> + Dir = filename:join(proplists:get_value(data_dir,Config), "new_format"), + [{fmt,new_format}, + {key_src_dir,Dir} | Config]; + +init_per_group(old_format, Config) -> + Dir = filename:join(proplists:get_value(data_dir,Config), "old_format"), + [{fmt,old_format}, + {key_src_dir,Dir} | Config]; + +init_per_group(passphrase, Config0) -> + case supported(hashs, md5) of + true -> + Dir = filename:join(proplists:get_value(data_dir,Config0), "old_format_passphrase"), + PassPhrases = [{K,"somepwd"} || K <- [dsa_pass_phrase, + rsa_pass_phrase, + ecdsa_pass_phrase]], + Config1 = extend_optsL(client_opts, PassPhrases, Config0), + replace_opt(key_src_dir, Dir, Config1); + false -> + {skip, "Unsupported hash"} + end; + +init_per_group(_, Config) -> + Config. + + +extend_opts(OptName, Value, Config) -> + Opts = proplists:get_value(OptName, Config), + replace_opt(OptName, [Value|Opts], Config). + +extend_optsL(OptName, Values, Config) -> + Opts = proplists:get_value(OptName, Config), + replace_opt(OptName, Values ++ Opts, Config). + +replace_opt(OptName, Value, Config) -> + lists:keyreplace(OptName, 1, Config, {OptName,Value}). + + + +end_per_group(_, Config) -> + Config. + +%%%---------------------------------------------------------------- +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%%---------------------------------------------------------------- +%%% Test Cases ---------------------------------------------------- +%%%---------------------------------------------------------------- +connect_rsa_to_rsa(Config0) -> + Config = setup_user_system_dir(rsa, rsa, Config0), + try_connect(Config). + +connect_rsa_to_dsa(Config0) -> + Config = setup_user_system_dir(rsa, dsa, Config0), + try_connect(Config). + +connect_rsa_to_ecdsa(Config0) -> + Config = setup_user_system_dir(rsa, ecdsa, Config0), + try_connect(Config). + +connect_rsa_to_ed25519(Config0) -> + Config = setup_user_system_dir(rsa, ed25519, Config0), + try_connect(Config). + +connect_dsa_to_rsa(Config0) -> + Config = setup_user_system_dir(dsa, rsa, Config0), + try_connect(Config). + +connect_dsa_to_dsa(Config0) -> + Config = setup_user_system_dir(dsa, dsa, Config0), + try_connect(Config). + +connect_dsa_to_ecdsa(Config0) -> + Config = setup_user_system_dir(dsa, ecdsa, Config0), + try_connect(Config). + +connect_dsa_to_ed25519(Config0) -> + Config = setup_user_system_dir(dsa, ed25519, Config0), + try_connect(Config). + +connect_ecdsa_to_rsa(Config0) -> + Config = setup_user_system_dir(ecdsa, rsa, Config0), + try_connect(Config). + +connect_ecdsa_to_dsa(Config0) -> + Config = setup_user_system_dir(ecdsa, dsa, Config0), + try_connect(Config). + +connect_ecdsa_to_ecdsa(Config0) -> + Config = setup_user_system_dir(ecdsa, ecdsa, Config0), + try_connect(Config). + +connect_ecdsa_to_ed25519(Config0) -> + Config = setup_user_system_dir(ecdsa, ed25519, Config0), + try_connect(Config). + +connect_ed25519_to_rsa(Config0) -> + Config = setup_user_system_dir(ed25519, rsa, Config0), + try_connect(Config). + +connect_ed25519_to_dsa(Config0) -> + Config = setup_user_system_dir(ed25519, dsa, Config0), + try_connect(Config). + +connect_ed25519_to_ecdsa(Config0) -> + Config = setup_user_system_dir(ed25519, ecdsa, Config0), + try_connect(Config). + +connect_ed25519_to_ed25519(Config0) -> + Config = setup_user_system_dir(ed25519, ed25519, Config0), + try_connect(Config). + + +%%%---------------------------------------------------------------- +try_connect({skip,Reson}) -> + {skip,Reson}; +try_connect(Config) -> + SystemDir = proplists:get_value(system_dir, Config), + UserDir = proplists:get_value(user_dir, Config), + ClientOpts = proplists:get_value(client_opts, Config, []), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}]), + C = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir}, + {silently_accept_hosts, true}, + {user_interaction, false} + | ClientOpts]), + ssh:close(C), + ssh:stop_daemon(Pid). + +%%%---------------------------------------------------------------- +%%% Local --------------------------------------------------------- +%%%---------------------------------------------------------------- +setup_user_system_dir(ClientAlg, ServerAlg, Config) -> + case supported(public_keys, ClientAlg) andalso supported(public_keys, ServerAlg) of + true -> + PrivDir = proplists:get_value(priv_dir, Config), + KeySrcDir = proplists:get_value(key_src_dir, Config), + Fmt = proplists:get_value(fmt, Config), + + System = lists:concat(["system_", ClientAlg, "_", ServerAlg, "_", Fmt]), + SystemDir = filename:join(PrivDir, System), + file:make_dir(SystemDir), + + User = lists:concat(["user_", ClientAlg, "_", ServerAlg, "_", Fmt]), + UserDir = filename:join(PrivDir, User), + file:make_dir(UserDir), + + HostSrcFile = filename:join(KeySrcDir, file(host,ServerAlg)), + HostDstFile = filename:join(SystemDir, file(host,ServerAlg)), + {ok,_} = file:copy(HostSrcFile, HostDstFile), + + UserSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)), + UserDstFile = filename:join(UserDir, file(user,ClientAlg)), + {ok,_} = file:copy(UserSrcFile, UserDstFile), + + UserPubSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)++".pub"), + AuthorizedKeys = filename:join(UserDir, "authorized_keys"), + {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys), + + [{system_dir,SystemDir}, + {user_dir,UserDir} | Config]; + + false -> + {skip, unsupported_algorithm} + end. + +%%%---------------------------------------------------------------- +file(host, dsa) -> "ssh_host_dsa_key"; +file(host, ecdsa) -> "ssh_host_ecdsa_key"; +file(host, ed25519) -> "ssh_host_ed25519_key"; +file(host, rsa) -> "ssh_host_rsa_key"; +file(user, dsa) -> "id_dsa"; +file(user, ecdsa) -> "id_ecdsa"; +file(user, ed25519) -> "id_ed25519"; +file(user, rsa) -> "id_rsa". + + +supported(public_keys, dsa) -> supported(public_keys, dss); +supported(public_keys, ed448) -> supported(public_keys, eddsa); +supported(public_keys, ed25519) -> supported(public_keys, eddsa); +supported(Type, Alg) -> lists:member(Alg, crypto:supports(Type)). + diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa new file mode 100644 index 0000000000..5167322957 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa @@ -0,0 +1,21 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsgAAAAdzc2gtZH +NzAAAAgQDsCchYjb27VZTDJuPwnYQQYZBCnQlVROAfDHa1dmAIQswOm/xFu5RyVpYCQPOY +Y2OBdEibM7/FzaAJUs9gRLfbcCHA9jYy8zjag5KijtPJe9BxoDPAM8bSEFfLDklebS8NDZ +tZvHsSz4UwagBQKNvoTPjljsf7fgjaQ9735S2jmwAAABUAiLuwAwdJr1qlGSmJSFqeM0Ao +18UAAACAMA+NIBNjhzYr4nIhWv1x0TYZ8OldFIEh5iUDRf53ZxcdCloxtfNpZjYJbwEwLK +UUW6xcPEp7/nCVOd50Yk4HgaDV5YvHwhS7g2yFXoK2gHKSa42BfWR4c8fPoMfapWSJ0aQU +xGqebjTDeavwJq5umZCbk9/MfIcSctT9Pn88BncAAACBANMOLq9WhZs3LtechqTpFmXgzQ +zjtoqFYbOd1ERDMXBffyS12aAlrJ1uUTKA1P/XVrIUMNuuXapWY6QwmqSOFVD58QQx9Z4l +itZUu4H+lOSVPHpJrMq45hqmr4Mu35ENtxzNkfQI544/QLlfqkXJ3SME2tUUXAHvTHEnUY +UvvteXAAAB8BiO51oYjudaAAAAB3NzaC1kc3MAAACBAOwJyFiNvbtVlMMm4/CdhBBhkEKd +CVVE4B8MdrV2YAhCzA6b/EW7lHJWlgJA85hjY4F0SJszv8XNoAlSz2BEt9twIcD2NjLzON +qDkqKO08l70HGgM8AzxtIQV8sOSV5tLw0Nm1m8exLPhTBqAFAo2+hM+OWOx/t+CNpD3vfl +LaObAAAAFQCIu7ADB0mvWqUZKYlIWp4zQCjXxQAAAIAwD40gE2OHNiviciFa/XHRNhnw6V +0UgSHmJQNF/ndnFx0KWjG182lmNglvATAspRRbrFw8Snv+cJU53nRiTgeBoNXli8fCFLuD +bIVegraAcpJrjYF9ZHhzx8+gx9qlZInRpBTEap5uNMN5q/Amrm6ZkJuT38x8hxJy1P0+fz +wGdwAAAIEA0w4ur1aFmzcu15yGpOkWZeDNDOO2ioVhs53UREMxcF9/JLXZoCWsnW5RMoDU +/9dWshQw265dqlZjpDCapI4VUPnxBDH1niWK1lS7gf6U5JU8ekmsyrjmGqavgy7fkQ23HM +2R9Ajnjj9AuV+qRcndIwTa1RRcAe9McSdRhS++15cAAAAUfRVtNnUPI+lhQgeu4d1i+H8x +Y9UAAAATdWFiaG5pbEBlbHhhZGxqM3EzMgECAwQFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub new file mode 100644 index 0000000000..2f155b6ac9 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAOwJyFiNvbtVlMMm4/CdhBBhkEKdCVVE4B8MdrV2YAhCzA6b/EW7lHJWlgJA85hjY4F0SJszv8XNoAlSz2BEt9twIcD2NjLzONqDkqKO08l70HGgM8AzxtIQV8sOSV5tLw0Nm1m8exLPhTBqAFAo2+hM+OWOx/t+CNpD3vflLaObAAAAFQCIu7ADB0mvWqUZKYlIWp4zQCjXxQAAAIAwD40gE2OHNiviciFa/XHRNhnw6V0UgSHmJQNF/ndnFx0KWjG182lmNglvATAspRRbrFw8Snv+cJU53nRiTgeBoNXli8fCFLuDbIVegraAcpJrjYF9ZHhzx8+gx9qlZInRpBTEap5uNMN5q/Amrm6ZkJuT38x8hxJy1P0+fzwGdwAAAIEA0w4ur1aFmzcu15yGpOkWZeDNDOO2ioVhs53UREMxcF9/JLXZoCWsnW5RMoDU/9dWshQw265dqlZjpDCapI4VUPnxBDH1niWK1lS7gf6U5JU8ekmsyrjmGqavgy7fkQ23HM2R9Ajnjj9AuV+qRcndIwTa1RRcAe9McSdRhS++15c= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa new file mode 100644 index 0000000000..16f8d330e9 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQ6WBbp8A+ypG22sM4pqd+973IoBF9a +TcWU237H8NQRf4BbAKlA+NFfi5XHYrRtpW7XCTkdTQn1jdayyq8RtYaGAAAAsHHJLVBxyS +1QAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDpYFunwD7Kkbbaw +zimp373vcigEX1pNxZTbfsfw1BF/gFsAqUD40V+LlcditG2lbtcJOR1NCfWN1rLKrxG1ho +YAAAAhAIDK4KZm7jhx4e0gRH/DlLg8WYu+BUDNjmMAgcMLsDv9AAAAE3VhYmhuaWxAZWx4 +YWRsajNxMzIBAgME +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub new file mode 100644 index 0000000000..162ebd3fb0 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDpYFunwD7Kkbbawzimp373vcigEX1pNxZTbfsfw1BF/gFsAqUD40V+LlcditG2lbtcJOR1NCfWN1rLKrxG1hoY= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519 b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519 new file mode 100644 index 0000000000..e2b83c0800 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBo2p2XftaIjxYLqNQeIcDrdejVxhTX+db3O5UFV5XdLwAAAJhZQvGOWULx +jgAAAAtzc2gtZWQyNTUxOQAAACBo2p2XftaIjxYLqNQeIcDrdejVxhTX+db3O5UFV5XdLw +AAAECiqNZLTp3o73H+B1VseAJKhEiGXf4otOH461y+sAwBCmjanZd+1oiPFguo1B4hwOt1 +6NXGFNf51vc7lQVXld0vAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub new file mode 100644 index 0000000000..4d2f9f3677 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGjanZd+1oiPFguo1B4hwOt16NXGFNf51vc7lQVXld0v uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa new file mode 100644 index 0000000000..243c17cfd2 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA6helL/aMwpp5cHOCZ5bB8tkpgiXykiV0U61t6K4h7fdud8zOyI9e +N/H9l787QkzOoLBJxzFvTOYcqsC1CMYKNiCvPEtFDZyfD6tZv03nEOc3VZhEO9+pKmnW3y +/7xjRekEW3bqyEk3GkMhnDFfm3JILtZmdRrooXCkVGZaVxELm8N+cEtAE8TCbOIOo1mjsv +x+R8bGktx057EZ8Ieg8tFWh/atFemHj75ZILjY795ji5YQHBHfDUBAwNNY5lCsue4COT6Z +Udv03JgwnCVTu+XsXqH6yemjefU7nvWktudnaKGbg5OS/H2pDrm6ivjRtGdz0gkMiD9miw +Q2n03cFThQAAA8h66iCmeuogpgAAAAdzc2gtcnNhAAABAQDqF6Uv9ozCmnlwc4JnlsHy2S +mCJfKSJXRTrW3oriHt9253zM7Ij1438f2XvztCTM6gsEnHMW9M5hyqwLUIxgo2IK88S0UN +nJ8Pq1m/TecQ5zdVmEQ736kqadbfL/vGNF6QRbdurISTcaQyGcMV+bckgu1mZ1GuihcKRU +ZlpXEQubw35wS0ATxMJs4g6jWaOy/H5HxsaS3HTnsRnwh6Dy0VaH9q0V6YePvlkguNjv3m +OLlhAcEd8NQEDA01jmUKy57gI5PplR2/TcmDCcJVO75exeofrJ6aN59Tue9aS252dooZuD +k5L8fakOubqK+NG0Z3PSCQyIP2aLBDafTdwVOFAAAAAwEAAQAAAQA0vFbuUzCqtnodJyh9 +hazztJBxTXM0EVP/ddaI0JG8Nj2gp3b+H64uFEn44Y/MA9mYwZ4dTbmxLTXQEdG2xEaQox +RXFO3dfycmNIfnXPltCWmh0sesZVqKv4U0im7B3BJhlhMYz6yeOr+uubcFQFhN1WD97NCt +7VX7blfJlle+WGsH6IB728MSlo+pU7desTPaWIamsDTftUkzrIVvgbppmk79XX/zhObIi/ +8xR3cCxygq3LM31LwTOcEnRvMufJOv3lR0ybUE5INtJVYYpqZ2hFaNON+hOTxaAFZ/pJJ1 +zm5XTb9HWSaouLlalzkQONCTucp7qU7PnBnIB/nQ5A0BAAAAgDO683uK2WICRSr2J7E1Q3 +jQ+GqU0c4SslORQ4mwjMbJVMyQwd4U00ctex4XIoQdB6dn17jmvEJa7UiH0+TMm9cJefoL +V8G4O4XThEDIBtF7Kp1oKV9roxVdJ/iS7psMop3g9X+/9L4nOUMxlI21j7YKf3C5KJwhD3 +RAqyE/YClnAAAAgQD6BacnxjxmcFJdCevQcfIhSCwzXp7fjYWK46U9fBF5k+H+GUicfaL6 +LGp0DDBhhpQ/lAzNO5IMn6pPAfl0ZSOsGie9OszSpMujxG1fSTDeUqyf0hL+t7yVrCRTEc +cyl5t0JBSUyoHy0w1B3pXF2vjRtrmqFvTmTrWYJAHxbPePsQAAAIEA77B9KyiCwX8AKubN +AA+nRRdmJfju7yM0xiHO6RfOgSnp9EAMKyoUdVFY1yddxgTQvpGSSigBogJHLB2ptGtmTE +6DtIEtP0SDQGso/Q51AhTtk4ScYlPvZJiXVfRmyibVdxmQqa8+aJcqS0j1cReSmAu5zRQm +zW0/752JsHk3qhUAAAATdWFiaG5pbEBlbHhhZGxqM3EzMg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub new file mode 100644 index 0000000000..3042323a71 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDqF6Uv9ozCmnlwc4JnlsHy2SmCJfKSJXRTrW3oriHt9253zM7Ij1438f2XvztCTM6gsEnHMW9M5hyqwLUIxgo2IK88S0UNnJ8Pq1m/TecQ5zdVmEQ736kqadbfL/vGNF6QRbdurISTcaQyGcMV+bckgu1mZ1GuihcKRUZlpXEQubw35wS0ATxMJs4g6jWaOy/H5HxsaS3HTnsRnwh6Dy0VaH9q0V6YePvlkguNjv3mOLlhAcEd8NQEDA01jmUKy57gI5PplR2/TcmDCcJVO75exeofrJ6aN59Tue9aS252dooZuDk5L8fakOubqK+NG0Z3PSCQyIP2aLBDafTdwVOF uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key new file mode 100644 index 0000000000..071d761e46 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key @@ -0,0 +1,21 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH +NzAAAAgQDQp0Dp9ne0IXxq93hFwqQi+iOTNqw0GeJlCQvzSYsnLgPzWCj/4o1R6czLdx4/ +6nNWPJVmbManC0gcBwdcJbVXW7nTbW3xnD2LmzyB9dwjka8CgfC/A87MkslY8YCt0ZHz8f +/9L9eRqAv5udHlxgtaX1u81VDVPxAk4bOAAyz4AwAAABUA0pu1vOhk0mBbY9cz+uN+ELA2 +20MAAACAW1PJ1Rau3yNLwzldcoejYt4gOKmAoAUWL9F3fp+IKGhYf6Z6GX5OI/98BE2wWu +3/Kk12K7ZtVyTj2B1JheNjzivsZryNhZwJlCCbFQalkU9C2tApALix2j/PYQy++Hefk8yK +4qFwSG9DTC/TvMKPjaZA5w3TjIkWuC0tGoqvkkwAAACAIRynnqPLhvlyD0wg5F4v7iG+fr +d1JfusrjLobdgBz+d60wyu+0IUuaJdhB1z0TjPdxmkwwrbOzmkAhcRbHduV8HTAY/l0Trp +X8E4b7gzpJwcy/2T1lPx5pIwlcd3TwqwMBQPNOXV8FR2fZYhIARSZy0ccvePc+/XKFmKT6 +jjBCcAAAHwPSMhRT0jIUUAAAAHc3NoLWRzcwAAAIEA0KdA6fZ3tCF8avd4RcKkIvojkzas +NBniZQkL80mLJy4D81go/+KNUenMy3ceP+pzVjyVZmzGpwtIHAcHXCW1V1u5021t8Zw9i5 +s8gfXcI5GvAoHwvwPOzJLJWPGArdGR8/H//S/XkagL+bnR5cYLWl9bvNVQ1T8QJOGzgAMs ++AMAAAAVANKbtbzoZNJgW2PXM/rjfhCwNttDAAAAgFtTydUWrt8jS8M5XXKHo2LeIDipgK +AFFi/Rd36fiChoWH+mehl+TiP/fARNsFrt/ypNdiu2bVck49gdSYXjY84r7Ga8jYWcCZQg +mxUGpZFPQtrQKQC4sdo/z2EMvvh3n5PMiuKhcEhvQ0wv07zCj42mQOcN04yJFrgtLRqKr5 +JMAAAAgCEcp56jy4b5cg9MIOReL+4hvn63dSX7rK4y6G3YAc/netMMrvtCFLmiXYQdc9E4 +z3cZpMMK2zs5pAIXEWx3blfB0wGP5dE66V/BOG+4M6ScHMv9k9ZT8eaSMJXHd08KsDAUDz +Tl1fBUdn2WISAEUmctHHL3j3Pv1yhZik+o4wQnAAAAFQCWaj2vQN6mnxfVgP8YzlPakzQI +WAAAABN1YWJobmlsQGVseGFkbGozcTMyAQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..97193b1c58 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBANCnQOn2d7QhfGr3eEXCpCL6I5M2rDQZ4mUJC/NJiycuA/NYKP/ijVHpzMt3Hj/qc1Y8lWZsxqcLSBwHB1wltVdbudNtbfGcPYubPIH13CORrwKB8L8DzsySyVjxgK3RkfPx//0v15GoC/m50eXGC1pfW7zVUNU/ECThs4ADLPgDAAAAFQDSm7W86GTSYFtj1zP6434QsDbbQwAAAIBbU8nVFq7fI0vDOV1yh6Ni3iA4qYCgBRYv0Xd+n4goaFh/pnoZfk4j/3wETbBa7f8qTXYrtm1XJOPYHUmF42POK+xmvI2FnAmUIJsVBqWRT0La0CkAuLHaP89hDL74d5+TzIrioXBIb0NML9O8wo+NpkDnDdOMiRa4LS0aiq+STAAAAIAhHKeeo8uG+XIPTCDkXi/uIb5+t3Ul+6yuMuht2AHP53rTDK77QhS5ol2EHXPROM93GaTDCts7OaQCFxFsd25XwdMBj+XROulfwThvuDOknBzL/ZPWU/HmkjCVx3dPCrAwFA805dXwVHZ9liEgBFJnLRxy949z79coWYpPqOMEJw== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key new file mode 100644 index 0000000000..3d01041bed --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQT21rZj/MTJN9fK+jDZ6l0bYF1EndNL +N+3a4cAYl8jRhRk18s8QbCN3GP+CsFWtor5fRhragFo2X7yPVAVU75FoAAAAsEntb7pJ7W ++6AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPbWtmP8xMk318r6 +MNnqXRtgXUSd00s37drhwBiXyNGFGTXyzxBsI3cY/4KwVa2ivl9GGtqAWjZfvI9UBVTvkW +gAAAAhAJVvBMRkEfuS8/YZ9PayecITNQ5CfZ5I49z3Ay17cxbbAAAAE3VhYmhuaWxAZWx4 +YWRsajNxMzIBAgME +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub new file mode 100644 index 0000000000..b125971661 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPbWtmP8xMk318r6MNnqXRtgXUSd00s37drhwBiXyNGFGTXyzxBsI3cY/4KwVa2ivl9GGtqAWjZfvI9UBVTvkWg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key new file mode 100644 index 0000000000..0ebf1dc8b7 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBKsbrzown8TYrOXlZYV/D+LICEyw23bwjdXRGfm4FEAwAAAJheMIX9XjCF +/QAAAAtzc2gtZWQyNTUxOQAAACBKsbrzown8TYrOXlZYV/D+LICEyw23bwjdXRGfm4FEAw +AAAEBxNZQd4RXl1DDYt+bm7WeSXlVmncu/bQ/ubdj56T2xV0qxuvOjCfxNis5eVlhX8P4s +gITLDbdvCN1dEZ+bgUQDAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..0108498194 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEqxuvOjCfxNis5eVlhX8P4sgITLDbdvCN1dEZ+bgUQD uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key new file mode 100644 index 0000000000..6d51bf7672 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAt+8ElruXAw+OXtyGBUMxAkSfsw/A7BWqIzbtRn6hD3YlJPFYBo4O +0wNeW4g9z+k5u42Q4Sv3gZAPuVAVi/E4MEd3eg8ZRkkC674a5lUvEzIrDYPbkhzR5bLidG +0TK4wdqL/HRGE9gpTIaOcIDkK2RI2PeRhjvjscUHTsa/Kx6dwBiKOK5vjIXA2MlsTgVY7N +MSpjR/sH/TG6UZXxhlgmBGsrpVsVM/3kQomhVVoMn1yRG6Wz2Ed+b6ENYKNmuomeIoCZoU ++uE0RMqiE5qD32YVExt1B89CVAR3GbQKKTop+Pb1xkNMnYvCpDo8+68dZ7U7zJ1SZr00yd +G0M4pFntDwAAA9At3ILHLdyCxwAAAAdzc2gtcnNhAAABAQC37wSWu5cDD45e3IYFQzECRJ ++zD8DsFaojNu1GfqEPdiUk8VgGjg7TA15biD3P6Tm7jZDhK/eBkA+5UBWL8TgwR3d6DxlG +SQLrvhrmVS8TMisNg9uSHNHlsuJ0bRMrjB2ov8dEYT2ClMho5wgOQrZEjY95GGO+OxxQdO +xr8rHp3AGIo4rm+MhcDYyWxOBVjs0xKmNH+wf9MbpRlfGGWCYEayulWxUz/eRCiaFVWgyf +XJEbpbPYR35voQ1go2a6iZ4igJmhT64TREyqITmoPfZhUTG3UHz0JUBHcZtAopOin49vXG +Q0ydi8KkOjz7rx1ntTvMnVJmvTTJ0bQzikWe0PAAAAAwEAAQAAAQBjIt2rTIJxMOJAeMV3 +cqaonUoiHdySon6oKkOrGjc++SO+DKKwLcMJsqgZ143RUNhAIWY0Jxlo6LfA3swuOB5bzz +kzPY4W1uVPIJCpEsKjqwePakFfOE9daZQqwltxvjyCJpOFZI/doMl/2P37ibNpsY7h6uZf +ssZpCwwehpmj/HknoAlrGTrG2SlzLTMvk6vwYgNoeHKCQfM2wfr9fFlbwNFJE7L44o0PfK +8UX9u8mC7PR1Q1u2OopOsUXWu6f1Vc/nU5La6Z6W1voHxMfUMLhq7c/Jih7SfPVX2z6U22 +VKGXinhh1q5a71nv44BZPxTGw0TC/FrDntTWyDu3jkuhAAAAgQCNZx4JVV+yoFnbypRHh+ +n0hhYvPbtHEzmhK9WEyjQCWIhf7zMXau+00bhDaS+6nqiZOfXPecC9UbjH06KT8da6yqYK +3SwsXA+RALbKe0uqWO1KKufge+FxZWX3j07D/8+pL1fE0996f9yjR06kM8b0afxQBMDxnG +HzVnPJEmUpjAAAAIEA50BnLTKqHsQCiBHRYuzRAXomeW7EWO+lQSB7WUcx91QtuzspoXMK +NyiaabzsPfTqmqRH9fcL1ORSTy4Jk+0tZjK20P+gGtGthHCxQjQnOhgyjvSzp0P7F55Cme +LSr1lYtFtjrh4nLval05ddQ6cYmebzRzy0NveKUbeIgkR5zJEAAACBAMuePyCBm2RB6Hlw +cYqUTJ+zduNFhu83YJ2vQg0ojJ5If9jHNrTHyiRG/k81AJS0fZs9aF6//fvq+85Bf3bGd6 +4PSWbSwjlxLNvbcGqfQ8gOk75lni2hr5e5SQBimGavJM7bjqr3+nqBvAiPrDROWQpALdbh +hVWNOMdo8JBxcW+fAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub new file mode 100644 index 0000000000..0b12e79f63 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC37wSWu5cDD45e3IYFQzECRJ+zD8DsFaojNu1GfqEPdiUk8VgGjg7TA15biD3P6Tm7jZDhK/eBkA+5UBWL8TgwR3d6DxlGSQLrvhrmVS8TMisNg9uSHNHlsuJ0bRMrjB2ov8dEYT2ClMho5wgOQrZEjY95GGO+OxxQdOxr8rHp3AGIo4rm+MhcDYyWxOBVjs0xKmNH+wf9MbpRlfGGWCYEayulWxUz/eRCiaFVWgyfXJEbpbPYR35voQ1go2a6iZ4igJmhT64TREyqITmoPfZhUTG3UHz0JUBHcZtAopOin49vXGQ0ydi8KkOjz7rx1ntTvMnVJmvTTJ0bQzikWe0P uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa new file mode 100644 index 0000000000..f76cc234b0 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvQIBAAKBgQD5eG+xea43Nas9xKOEPqkMaBmTZuKo+nz9S2mo3Qkuj5OV6Mqc +Jd6g9DUylc5XMtyYvq0AImgirc3ZTCOLyH3wXunNDkafUxwxrSO3w68mgdGbjJxZ +euS/dRx6pYmeb/ykWCQFd6D/t1OGK0QJT7gn9ke6pXAL1ARVafP2Yri8rQIVAORv +Zk6erYLd3aAvPNzVSLH6P6CRAoGBAImjtInOXhMXa+7ABuwrvdN78bJX5pSdlgUW +W1Nx1obUb65njR78CqB+fynwxevHDBPNEVE0T5xfAg3zWBTGH/mwQ/ivA3t350hT +usbRBbPs9kzzhfWdq7GKVKL76UmefYU1cwM58VdOFcuMbfUEujwIgw+KbJzI7c6y +DH8ll+s7AoGBAK8z5gZp4k43uEFRsLwhZj4edgb3vmZvOvEzo3awP+Pr2Et4ReL1 +48YYfb422FafzePzCkvkRNMdZF2iiPQXK1r4JIVCfi2Zyci8fMzHmBR3bmvz5cjP +9tVtKGR9z48dYe5R74Td+Vp//h6lCHyi06kSg8yoambE8sdAnp5DdgN6AhUAg8aR +exXtcrukzyrWLA/4jZSnrFY= +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub new file mode 100644 index 0000000000..20c42561b0 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAPl4b7F5rjc1qz3Eo4Q+qQxoGZNm4qj6fP1LaajdCS6Pk5Xoypwl3qD0NTKVzlcy3Ji+rQAiaCKtzdlMI4vIffBe6c0ORp9THDGtI7fDryaB0ZuMnFl65L91HHqliZ5v/KRYJAV3oP+3U4YrRAlPuCf2R7qlcAvUBFVp8/ZiuLytAAAAFQDkb2ZOnq2C3d2gLzzc1Uix+j+gkQAAAIEAiaO0ic5eExdr7sAG7Cu903vxslfmlJ2WBRZbU3HWhtRvrmeNHvwKoH5/KfDF68cME80RUTRPnF8CDfNYFMYf+bBD+K8De3fnSFO6xtEFs+z2TPOF9Z2rsYpUovvpSZ59hTVzAznxV04Vy4xt9QS6PAiDD4psnMjtzrIMfyWX6zsAAACBAK8z5gZp4k43uEFRsLwhZj4edgb3vmZvOvEzo3awP+Pr2Et4ReL148YYfb422FafzePzCkvkRNMdZF2iiPQXK1r4JIVCfi2Zyci8fMzHmBR3bmvz5cjP9tVtKGR9z48dYe5R74Td+Vp//h6lCHyi06kSg8yoambE8sdAnp5DdgN6 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa new file mode 100644 index 0000000000..e0ed11843a --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINRRCrEPoBUJJueQ6IDc7UxpvYi+gzBJkn3pv+yp08tvoAoGCCqGSM49 +AwEHoUQDQgAEKAnppzgUZKpYf6qLbLL1LmTLWPLI7NDjY/+oWE1NqrUrMOM4NXKG +NCTNhLKWtICjfb8h0E3zbCh5PNubVw14WQ== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub new file mode 100644 index 0000000000..2793c68b70 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCgJ6ac4FGSqWH+qi2yy9S5ky1jyyOzQ42P/qFhNTaq1KzDjODVyhjQkzYSylrSAo32/IdBN82woeTzbm1cNeFk= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa new file mode 100644 index 0000000000..30871b6a67 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtnd1zCAPBkxEHFBQjpt9TOY7epRozetWWBp1wVQYxHMicpwe +2NwuN+3A6Y5r20lhdaElVQ08DBEcJ184sSxv/+8PCu4umiSyqRyrUvdap60InZq1 +Fg50OJMfAGcWjeOp4UrH/nGdJyqdN2Y40eVsXg4pogy7ElG3Q5hROmbC9vpOTkrb +3Krv7JFFXz3vhRkxiYCMNw8hK8TOye9o1JCyVzImZGGwdUA9OZN4Nizoj6ie90KC +VRoI+2GoE0gNy4Mt7P+lOyDBzFeJbGl0ob9S5P3gnyCVUmlV0EY1fSijN8IIOj5l +qsBpADt+sN3iYltZJmCn5wDP/iORn6P04jWU1wIDAQABAoIBAHlncHw5lGWXVvYT +xhWshSkmQsrjdfwUqmWCbXkNkFEdXf0tvSSDE0lpKqL7fO3xnCPc7W7ymFJbDAVy +SNExhO+fyr12DpHG+wykI6XXKH1KFuJuLjCXu2JtGQJ2lL4hjUV2MS0twOdvZh2X +KRUW9gx6ld7ZY5rjvfD+poUaHHygnN6f0+PiyBpUZaL+ZTj/6CpHiCxdZtOCf0o6 +bU7TaPNcZ3vf3Qhk4jk140vEDLJQnPF8stBqPWa9HfmM7CNjWBdmhQXHtHw8CtF6 +aba9BRC/FMYx43itE9hkg6p3JrSqAN/gZ6RCXLog6mQYttJV+y8oLTXvblT2Y3c9 +YjnigGkCgYEA2Y4+sjsGw3l5HnQ9CNuUQvJZgloVd/NTw8/UXXkV1QudI6tzmMJn +XAxCCtt2DVlPBqFJ74uwdWpY0nwFJslEp4sV6VPv2TpBmAOPDB6QtQ8SXhFWQ7vj +BVh5kwl3LUDVI+NIACoaMyZuS6N97Fp4a/mKtgj+ucOkfr15+IF7L/UCgYEA1rXd +ATBFQlLoPuv3VglU1LLgaLs/3qzoBH9DzwPPyEFdWYQUCuMHsL9eEfDZMsG/GeZb +Fch/CR0R2Qt+ZlcxcRichgMw3ydzIBqCvLe444lBzxdLWFqS5gCnSh5iage2QRs0 ++6QD9O16HJER6HmBwR6DtwpP3N4dHxLXJRjDrhsCgYEAl3/M/UTJkvpWc/SyRCbU ++kHWP0YIST2ziVqDIoydvXyW8y4EE87dN2Z53yGw9d7Jf252FFCMk1d5fypKVBY4 +rwvWOGPxVK6S2w8vYFswnkVenw8nqYd/sktIbjJbQbIyOwmdLDAlipUqnZW+rQbb +cSWXiOh+qlIpjPDZrUpNxLkCgYAMPjiI7dC1NHcLx3bGECgnLMABGNROhTuBriQW +tNfvSlLhXNeru0BgArmBemNYMpYMCwecmV8tDNxMrQwbF9O46DdcqOfrgZtd9EUK +L8u6JcR7448nTZrcxKLFZjAkbaYl1kBSLQsQt03kPR1xTSRp96x5Dnx5Uq0EbZWZ +Bu15iwKBgAlSiCUqCNir3fdd0wE2+MIczam3YshQmnS3/XEk+7Bmfmb7Rxdwk94l +P/SaQYZ3buCKoBTz5OveBl4aEdiyvEqBkeJoUbzwFILyo0RNncqULcrYAPIJtbKK +H0o0naCZUgUJGsX0/DdJsEE27KMljc1A1Fpd3qQ2qqVLFfDTrsuB +-----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub new file mode 100644 index 0000000000..f7b1180aad --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2d3XMIA8GTEQcUFCOm31M5jt6lGjN61ZYGnXBVBjEcyJynB7Y3C437cDpjmvbSWF1oSVVDTwMERwnXzixLG//7w8K7i6aJLKpHKtS91qnrQidmrUWDnQ4kx8AZxaN46nhSsf+cZ0nKp03ZjjR5WxeDimiDLsSUbdDmFE6ZsL2+k5OStvcqu/skUVfPe+FGTGJgIw3DyErxM7J72jUkLJXMiZkYbB1QD05k3g2LOiPqJ73QoJVGgj7YagTSA3Lgy3s/6U7IMHMV4lsaXShv1Lk/eCfIJVSaVXQRjV9KKM3wgg6PmWqwGkAO36w3eJiW1kmYKfnAM/+I5Gfo/TiNZTX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key new file mode 100644 index 0000000000..99da9723ef --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDLRBMHSgExTaMVjXrZwxdgmkpZgjQXGy/IUqBNw9MTq1AmndGH +6Pkj3LzPiNsZArTwg3k0strcx+VtbQcJ1TrH7nOABdO5vTGAd9arPIkhts0LgwHA +HcAHf+9iNr26uG8jtIc7+o7IpJyTzy2buVmfosYhKcwVbPHIhQp8KjAWfQIVAPd+ +7YzKkxRyBPZZ4K+G3QI3YJiPAoGBAKGRI74EwgLwRWMeVOJun7oWh0uVQFdi51sY +3e9JC4/0hx0D0JHO2+b3opGPw/wrVXmGWZdmgpslsPZbta/oVR0OgAK4V2tTgn8p +ijOIcEJmvpogtaJj40laTV+mpwRfQZWsfJPYTibZqtPVvvtu+6eVB1dTlNYngOAN +mDHh6wpzAoGATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQB +Dmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QB +dBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJcCFHmpYuRa +y/ynGrBb/mzt9QAGhNls +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..485997cfb9 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMtEEwdKATFNoxWNetnDF2CaSlmCNBcbL8hSoE3D0xOrUCad0Yfo+SPcvM+I2xkCtPCDeTSy2tzH5W1tBwnVOsfuc4AF07m9MYB31qs8iSG2zQuDAcAdwAd/72I2vbq4byO0hzv6jsiknJPPLZu5WZ+ixiEpzBVs8ciFCnwqMBZ9AAAAFQD3fu2MypMUcgT2WeCvht0CN2CYjwAAAIEAoZEjvgTCAvBFYx5U4m6fuhaHS5VAV2LnWxjd70kLj/SHHQPQkc7b5veikY/D/CtVeYZZl2aCmyWw9lu1r+hVHQ6AArhXa1OCfymKM4hwQma+miC1omPjSVpNX6anBF9Blax8k9hOJtmq09W++277p5UHV1OU1ieA4A2YMeHrCnMAAACATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQBDmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QBdBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJc= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key new file mode 100644 index 0000000000..75ba71da56 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJeZs+jukpumHYLii4OXg5k9dN7u1aNLZovxbqFoEfgToAoGCCqGSM49 +AwEHoUQDQgAEBg0zAjfzxl0ccv2wnJHZvLXETa6bopctXD1V/FWcPoBL5dh42mOj +I6ZgtrUnnjbhdxJyeG3BjntqhP5rLMMpeA== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub new file mode 100644 index 0000000000..26e7285240 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAYNMwI388ZdHHL9sJyR2by1xE2um6KXLVw9VfxVnD6AS+XYeNpjoyOmYLa1J5424XcScnhtwY57aoT+ayzDKXg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key new file mode 100644 index 0000000000..6044fc7725 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArrzkBUAMYvAso4hR79vmNbxNbLYt7QocukbiCWOq29HQvqbS +zj/OSE1Qg6C/aTbghvdpvxbaFt3aqdWroF0PhVVoEsJY76bI7RsobIe9zI/Z3dkQ +RmW3IyjHvpVBwy84fKZ05A8Bd10kca1GfrQXi7LkFZ5FRjxarFzMojGVesWcZLag +ThPG0QAjSw2sG+oql4YoIeagSdf3tzOOF+04vcpgqCRugsscw17lI1Rwq0nM3thU +BgSWDRbjzmkWo9i5Wpc9ZKS1z4ANET+I2hGW7PEA6XAXJKC6nIWdVIie0GN02C/m +i45NyTPn52I/TJKFAtIoZ8fbrHEepX7V/Dt7DQIDAQABAoIBAA1yJY2t3wYh+x1e +WQe/ARjzc3XBEwmhdJJ07+HPFI+ztn9lMOWEDWiM4nwue2wqN97K3Q1CQefujGvz +MDC32IDnEIoZAGT4jY+JPnQTgexiyV4D3Pe9zfjbo3sr2xKc6JjW6jm+WduIhExn +C/yl+QXb7ycmtafw7v1CatC0Rg9bUtE8nMHKYVPazn30wlHdPl6TyTtEXoZCKMg2 +OTxrva80x5JboUqLZXX41VqVmqqoakEO3NOGlhrIzIOB866py8d+f6wilN/rUcGe +MJwB8aTrYPxLkCYl9PGeMDMLARvhjMm53f6UQFDpL2rY0XpnaqrZqS9KrbJoTQDW +lMj3OiECgYEA4FL3fba4FYGzf7T/m2xRSC5qrpr0gNn1mZJ5oUWGiHBwjAHC7EVW +apcjskac7WrznIBJiV2ozzxQOgIymuYO9LX95G6b3nrkWhVXqUiyCkpdMSE/YVfA +iMc3Z0QNz+I/cbEPUKZ7osKPZm4BRpUNvJwj4Vvt5jXwTZlPmVmpNHUCgYEAx2lx +q6HO+Grba0Azg7wobnnZ8uZZvdZ+QpxgGhH1Rx9862KM+uVYrJ7xEUlNTYfBtdpq +JXQnGlpzjGPeziZjJxv7AgWbJA80aXtTH/oE9E0KMmGRzE2bQNR8kUZHU5S2e0u+ +x1DUyvKqyKSRByxUp8wj5QZGPOH4MPvCHVZF+TkCgYAg52qQERYtaWn36Ie5t4iw +qsZROD93CwGAdkDLDBSwvLV1g+igmYcUeXjt9HeeR5rWMOcYdBmH1FP8PkhH+kjl +UjCcqjDI0IPgRtMl7JjY85F53GOclq+SII6a4huYi5o8xfj2HoVyGVHJd4dOYBy0 +tr54lvBtXSoTZ9KKLuGn5QKBgBUy4XGkfvMrsO3C4ncTrpyn+YJ3+HxU7BE6vICo +/hE0iLwhOumFLhsTvn7e8wfV8cLaWERpB6smiHgZOdtie1HyCIobfHWl5CV+hcS1 +eIdcFURr2OsGKQYIUMHE3dpFyexrjfl0X1q/12YDEKPZk5pO+lXjh937C75xVR53 +SHMJAoGAS57QNKLFaWZunoMNuvvNAj7Z0q1JrFuLEwfnkG28g5+ov3wIBinPtlrr +3HaK6sny0hHLPoRP+fa6BVRaQhDzeeKDu6PqNEkNnocWPi79lxfk531EJQHOgQgX +yt7Ruq0TlBYs5wGrmtYXLKAGvcfyx9EoFs2Km1iNKqu6b/dbQXc= +-----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub new file mode 100644 index 0000000000..8e62458395 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuvOQFQAxi8CyjiFHv2+Y1vE1sti3tChy6RuIJY6rb0dC+ptLOP85ITVCDoL9pNuCG92m/FtoW3dqp1augXQ+FVWgSwljvpsjtGyhsh73Mj9nd2RBGZbcjKMe+lUHDLzh8pnTkDwF3XSRxrUZ+tBeLsuQVnkVGPFqsXMyiMZV6xZxktqBOE8bRACNLDawb6iqXhigh5qBJ1/e3M44X7Ti9ymCoJG6CyxzDXuUjVHCrScze2FQGBJYNFuPOaRaj2Llalz1kpLXPgA0RP4jaEZbs8QDpcBckoLqchZ1UiJ7QY3TYL+aLjk3JM+fnYj9MkoUC0ihnx9uscR6lftX8O3sN uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa new file mode 100644 index 0000000000..5420f40352 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa @@ -0,0 +1,15 @@ +-----BEGIN DSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,C5C1380D397337A7425EF5FE7B2A01B5 + +/xZ9L6iKTvN6AbZu3Uper6DzMxLdhGMix9UB9pq3JXJAElZRfP5LkAYBX+3mYYXu +VGYsMsHrPLtItgDQQ7DNhbo+IOoC3Id7gIssxitXaNbuDs91ahMANzYPeypV71+U +fgNnznaTIL2yl6pr342IFksCw1lt9L6Z3nnslUO/4mwbESL7nIe2WDpxdAxIYQSw +QetL8FoP0ohyMvNdrmooJ48oJylR3Uctir1Szb6Rg65LEuvasY0SbJhDaS+weN9c ++dRgPTrZmIhohEy5FT52JqsSaONxM0NVV8wmkw0uX642cYpPU1WkVDK5RwXExiR0 +5hORAu1lqdCGG8M1KNutn/8ASN+ger79Jccp9Ity9TP3I2RbSFJCR8byysIJoFdF +Pb6ju+SRmtMBmrIMaAQdO10zAeBdVq78ALiK78Jw8D2ciA1zRnzvzBCoQhKw4dxU +XmD4V2qBVUuYFKms6Z/LRnjvShooQgmCm4CtX7RehQFTlbrJJcrfEaJdrb84d9QK +uY/lY/47tSsyC8YIK9ta9uyLgvxOmPvfKSA5lJpTPk0oEeRJEmtrMzDhfB4tXR7p ++d1F5kWJlTVdsYOpd1li28LpAu/YJrop85QP75TV/F8= +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub new file mode 100644 index 0000000000..2ce1d597f4 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALpx1AMh2SgvU5KTZLYlMMADqt46X/7KrXNiM/JbCG2vsUg+Pf1k24J5DThLH7Ex0o5ghmd4xrCi9hMrkcNe+RI1KU2B68mIrddGYmL9nLCwWf4loKmzVnTY+RCjBT7YRKOT4H9B1gF33kpRellmd+x7zVFSM1DbEdKjuPA9wseFAAAAFQD1R2qkuiiy67GoE6nErIaFH7cOIwAAAIEAqcswmM+/NrpXfaEF+mCUN1ylnO/YF2arqJp+CvAzSB9VhGnk7+hAX6vbQ+iQg6pDL8OOtky5fYqwlGdLJ4rTMiZuOOYx8grxgUxjMCmLCq3exH5VpXQnbcffZ+Zrqv6HtpqenLZgJxxcNG27oH/n4CLSoQOw0+AeJ8/NzIU8A68AAACBAJnnmAXlF9QW408bw9t0hNZDtacPDdiRENJaSVsb37+gTQvJPMfr6YpvDh2PO0ODg9Omwqddm/YpLAQAJ2tWzRMuxoI18ikcFWPveetaDZS/1eEPrTY3SMitmevrdCXGSpdEuvidbXNdW874MtmP5E4hW4vVe7wJkCEZ0hCL8AcN uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa new file mode 100644 index 0000000000..3108bf688f --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,E2E108EACB898D6E55241106B2CDEC45 + +hAmOR9aVIF2RNJet/vFVGgsx9Jh1/r02FJEPMWcdfSMgxJMfjKfCpKDqY72+Idzs ++WVk/Nr6s/TzNZwOH4WJV2ObZ1EG9yClHPPnAYsgoog88JhW5RRwEaa5X7oHFmVc +zK1cixK5qc39SUiAGGn0PWcnx6qVVWd1KIWbqFzV5Hs= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub new file mode 100644 index 0000000000..8854f3be20 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGcNr/NDr9ueJUzkYyy/MdhK5CFFJKcTIYMod81dlhvhtynWgE7B/eW5hdKwuUm07FLu6lOw14s39VCQ/WVPox4= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa new file mode 100644 index 0000000000..a74e47862c --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,B965F2149914DA8A54DD98275B94EC1C + +QRDCx1Taj7lBQRhLiPs8WLLsSSd8AKK/mzRUOwuf3E0VdrP20LLtcjn5xm9+zj91 +BQgPZshoB4eP2j2exJfTOOIkBdzjF3gYz/QyrSJ8fjdIA/MDzHcrv+HDpMes6gCP +UTb4daKtBmae/fnRoUJtOJeyfQsJT4HqSRuGitvp+/47TdiD8N4GeBO/buMGEch0 +zw89/9w5bGrn7cPYo6mB5WWjxBcGM0ertCJ4x+uU+QRaohXnatgVpaydCYIzbhdS +/thmlNEFizDtfSAoZQlGv30DXQWsvoYdDVZLtRPvYnNifTrcPBkfy/Etl9cgwmFT ++3aF8pQKppdGSeR5vaT5ardjyWEhoM1hVlucWzBjbhlWLeozjzMyT74lxw3hSiJW +KJsoFHrFsfMB1G4cfI/gANbaFOc834w5GHxrz6PCHnWwDLulnhMTPDv1Pu6tg+8Z ++Dxu8pVIehnNUifj9OfPfY+ZNIbdroRavYr/YXB+16lyP1u4r1/rbIbBWTorebLw +8Z6Yc1q4svnhiytWoyfzF3UQotCucZ13tHN3OaQ/u0SQnckYU1SdlGJqSvLW8N7S +Yx5jW5mFbuOAQbvzs2CgeO/ooXq0KNVTYOkr3neN6PMk4WYcEsagmaqhMfIFVCBJ +Fc4GFXbkSO4KKqRysGnnEt4sI9TJX9uIBVTtg38Q4rGisGMejD4f7eTjwqSMy4Xy +ETF2+JQW5rJTnMstAqA1RvGxqpsZXNxx4+pLSeHTe0xIC+LuB92THXiD1a5xcQa3 +RS2s8CT4trOqKLLr22eRkV2XfEG+v1LEcp2uVyjBVM8CPKRP6w5KNnut19CA7AnQ +WVVeNRGdVEtvId/kq2J91ZGTUWkmjgapTY+xhFNX67Sx0kwuosb2N1SvR6FidWIH +d7xCHf/WO2aKuQgLv5fVvPqN4vPM/VMd8Vsx4y5IxUne0dkSYtk5iAeHwS+b3sti +sMQAEauF8clt+jUXSGX4vSLIAc8JYSLxDRr2yy6q8dzdQ77pqqcFgg7Fa6tLKNQ6 +Op1eknVzPRDUDyaDr0mlE9pnSde1g++mEwEca4VORLljEQpf6vYDD/bDnyEfTXoA +d1zt06X8OHAubtG8GcLilw9rMglWLthX+ymHqC1UWMOzgBpua6mffDb2I4qYWR29 +NPdEF1u0PKlzT7fqBE4tpiL4OY5lT/vcNDmf6cq59KKgSVYTzADTH/nQ6eLTkTgt +pU4L60NtCIsn5r2xqwMI8R2f9gjqIwjn45BkqqJBq2vdTlyW6m0vdUdYnGHRkA0c +fU1XOYUHUPi7Wa9MfcwyKYe55zMo7zspa0kIVOgkwfMVSnFr1eDM4jTNfaWFjIf0 +xEqnF6sOGO+KefL7nscJHfX7UipASXO3cWl26YvT5sJPCbwOZSNS3S+6+YCfimLn +kNxWOUBDuHMWOsUDc26lh7YtBaImsLrM8eqdz8vRP2V9WH5RoHu0WT43HMZkWYZd +FQeE5fNUPTAJb/PLOyh9LZQ4BibDE4ZbikEJfj1ut9+4C5aWt2NKmwSnDnJTCu3J +qUeSlk0zIZXLbRsnK6TBQLVXc4cnZrVeb/Dfb2fropioh0f/WFusO5TKuP88XcSG +-----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub new file mode 100644 index 0000000000..469a6ceb7c --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRAvJCrHYBhf5riHOyqBFfBCBZpKXf2zIc3iwbwY2xl/CHkEwZujBHe2KX1O1h8m7TpUNKMcdGNinsloZsDVKW2VEN7yT6oe3M2fyHLXDfd1KTyINF7LXqgtVq9+A8iLYdOBat7JBkY6c18pMvVU6yCWyr3m8+lnGskCW7swthANbwuW42PvyFdZf9/CkzNIVY6SsAF8wdXdf830wDIimZpN+ER8sLDGcu7dIaoypvs2KlCqwS6DE9kN7X2zXrxZA4thFsoJafgPDCaMOLRkCD2L8NBlRjXBE/U/sOb46oOfZTUy2wjo7pPwp9zekEO8l7OPZ9VgZ5bxXU+WTWox+z uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key new file mode 100644 index 0000000000..99da9723ef --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDLRBMHSgExTaMVjXrZwxdgmkpZgjQXGy/IUqBNw9MTq1AmndGH +6Pkj3LzPiNsZArTwg3k0strcx+VtbQcJ1TrH7nOABdO5vTGAd9arPIkhts0LgwHA +HcAHf+9iNr26uG8jtIc7+o7IpJyTzy2buVmfosYhKcwVbPHIhQp8KjAWfQIVAPd+ +7YzKkxRyBPZZ4K+G3QI3YJiPAoGBAKGRI74EwgLwRWMeVOJun7oWh0uVQFdi51sY +3e9JC4/0hx0D0JHO2+b3opGPw/wrVXmGWZdmgpslsPZbta/oVR0OgAK4V2tTgn8p +ijOIcEJmvpogtaJj40laTV+mpwRfQZWsfJPYTibZqtPVvvtu+6eVB1dTlNYngOAN +mDHh6wpzAoGATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQB +Dmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QB +dBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJcCFHmpYuRa +y/ynGrBb/mzt9QAGhNls +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..485997cfb9 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMtEEwdKATFNoxWNetnDF2CaSlmCNBcbL8hSoE3D0xOrUCad0Yfo+SPcvM+I2xkCtPCDeTSy2tzH5W1tBwnVOsfuc4AF07m9MYB31qs8iSG2zQuDAcAdwAd/72I2vbq4byO0hzv6jsiknJPPLZu5WZ+ixiEpzBVs8ciFCnwqMBZ9AAAAFQD3fu2MypMUcgT2WeCvht0CN2CYjwAAAIEAoZEjvgTCAvBFYx5U4m6fuhaHS5VAV2LnWxjd70kLj/SHHQPQkc7b5veikY/D/CtVeYZZl2aCmyWw9lu1r+hVHQ6AArhXa1OCfymKM4hwQma+miC1omPjSVpNX6anBF9Blax8k9hOJtmq09W++277p5UHV1OU1ieA4A2YMeHrCnMAAACATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQBDmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QBdBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJc= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key new file mode 100644 index 0000000000..75ba71da56 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJeZs+jukpumHYLii4OXg5k9dN7u1aNLZovxbqFoEfgToAoGCCqGSM49 +AwEHoUQDQgAEBg0zAjfzxl0ccv2wnJHZvLXETa6bopctXD1V/FWcPoBL5dh42mOj +I6ZgtrUnnjbhdxJyeG3BjntqhP5rLMMpeA== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub new file mode 100644 index 0000000000..26e7285240 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAYNMwI388ZdHHL9sJyR2by1xE2um6KXLVw9VfxVnD6AS+XYeNpjoyOmYLa1J5424XcScnhtwY57aoT+ayzDKXg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key new file mode 100644 index 0000000000..6044fc7725 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArrzkBUAMYvAso4hR79vmNbxNbLYt7QocukbiCWOq29HQvqbS +zj/OSE1Qg6C/aTbghvdpvxbaFt3aqdWroF0PhVVoEsJY76bI7RsobIe9zI/Z3dkQ +RmW3IyjHvpVBwy84fKZ05A8Bd10kca1GfrQXi7LkFZ5FRjxarFzMojGVesWcZLag +ThPG0QAjSw2sG+oql4YoIeagSdf3tzOOF+04vcpgqCRugsscw17lI1Rwq0nM3thU +BgSWDRbjzmkWo9i5Wpc9ZKS1z4ANET+I2hGW7PEA6XAXJKC6nIWdVIie0GN02C/m +i45NyTPn52I/TJKFAtIoZ8fbrHEepX7V/Dt7DQIDAQABAoIBAA1yJY2t3wYh+x1e +WQe/ARjzc3XBEwmhdJJ07+HPFI+ztn9lMOWEDWiM4nwue2wqN97K3Q1CQefujGvz +MDC32IDnEIoZAGT4jY+JPnQTgexiyV4D3Pe9zfjbo3sr2xKc6JjW6jm+WduIhExn +C/yl+QXb7ycmtafw7v1CatC0Rg9bUtE8nMHKYVPazn30wlHdPl6TyTtEXoZCKMg2 +OTxrva80x5JboUqLZXX41VqVmqqoakEO3NOGlhrIzIOB866py8d+f6wilN/rUcGe +MJwB8aTrYPxLkCYl9PGeMDMLARvhjMm53f6UQFDpL2rY0XpnaqrZqS9KrbJoTQDW +lMj3OiECgYEA4FL3fba4FYGzf7T/m2xRSC5qrpr0gNn1mZJ5oUWGiHBwjAHC7EVW +apcjskac7WrznIBJiV2ozzxQOgIymuYO9LX95G6b3nrkWhVXqUiyCkpdMSE/YVfA +iMc3Z0QNz+I/cbEPUKZ7osKPZm4BRpUNvJwj4Vvt5jXwTZlPmVmpNHUCgYEAx2lx +q6HO+Grba0Azg7wobnnZ8uZZvdZ+QpxgGhH1Rx9862KM+uVYrJ7xEUlNTYfBtdpq +JXQnGlpzjGPeziZjJxv7AgWbJA80aXtTH/oE9E0KMmGRzE2bQNR8kUZHU5S2e0u+ +x1DUyvKqyKSRByxUp8wj5QZGPOH4MPvCHVZF+TkCgYAg52qQERYtaWn36Ie5t4iw +qsZROD93CwGAdkDLDBSwvLV1g+igmYcUeXjt9HeeR5rWMOcYdBmH1FP8PkhH+kjl +UjCcqjDI0IPgRtMl7JjY85F53GOclq+SII6a4huYi5o8xfj2HoVyGVHJd4dOYBy0 +tr54lvBtXSoTZ9KKLuGn5QKBgBUy4XGkfvMrsO3C4ncTrpyn+YJ3+HxU7BE6vICo +/hE0iLwhOumFLhsTvn7e8wfV8cLaWERpB6smiHgZOdtie1HyCIobfHWl5CV+hcS1 +eIdcFURr2OsGKQYIUMHE3dpFyexrjfl0X1q/12YDEKPZk5pO+lXjh937C75xVR53 +SHMJAoGAS57QNKLFaWZunoMNuvvNAj7Z0q1JrFuLEwfnkG28g5+ov3wIBinPtlrr +3HaK6sny0hHLPoRP+fa6BVRaQhDzeeKDu6PqNEkNnocWPi79lxfk531EJQHOgQgX +yt7Ruq0TlBYs5wGrmtYXLKAGvcfyx9EoFs2Km1iNKqu6b/dbQXc= +-----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub new file mode 100644 index 0000000000..8e62458395 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuvOQFQAxi8CyjiFHv2+Y1vE1sti3tChy6RuIJY6rb0dC+ptLOP85ITVCDoL9pNuCG92m/FtoW3dqp1augXQ+FVWgSwljvpsjtGyhsh73Mj9nd2RBGZbcjKMe+lUHDLzh8pnTkDwF3XSRxrUZ+tBeLsuQVnkVGPFqsXMyiMZV6xZxktqBOE8bRACNLDawb6iqXhigh5qBJ1/e3M44X7Ti9ymCoJG6CyxzDXuUjVHCrScze2FQGBJYNFuPOaRaj2Llalz1kpLXPgA0RP4jaEZbs8QDpcBckoLqchZ1UiJ7QY3TYL+aLjk3JM+fnYj9MkoUC0ihnx9uscR6lftX8O3sN uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index 4ecec4e79d..a8436ab53b 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -86,7 +86,7 @@ groups() -> directory_to_tar, binaries_to_tar, null_crypto_tar, simple_crypto_tar_small, simple_crypto_tar_big, read_tar, read_null_crypto_tar, read_crypto_tar, - aes_cbc256_crypto_tar, aes_ctr_stream_crypto_tar + block_size_1_crypto_tar, block_size_16_crypto_tar ]}, {write_read_tests, [], [open_close_file, open_close_dir, read_file, read_dir, @@ -1017,54 +1017,10 @@ read_crypto_tar(Config) -> chk_tar(NameBins, Config, [{crypto,Cr}]). %%-------------------------------------------------------------------- -aes_cbc256_crypto_tar(Config) -> - ChPid2 = proplists:get_value(channel_pid2, Config), - NameBins = lists:sort( - [{"b1",<<"A binary">>}, - {"b2",list_to_binary(lists:duplicate(750000,"a"))}, - {"d1",fn("d1",Config)} % Dir - ]), - Key = <<"This is a 256 bit key. Boring...">>, - Ivec0 = crypto:strong_rand_bytes(16), - DataSize = 1024, % data_size rem 16 = 0 for aes_cbc - - Cinitw = fun() -> {ok, Ivec0, DataSize} end, - Cinitr = fun() -> {ok, Ivec0, DataSize} end, - - Cenc = fun(PlainBin,Ivec) -> - CipherBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin), - {ok, CipherBin, crypto:next_iv(aes_cbc,CipherBin), DataSize} - end, - Cdec = fun(CipherBin,Ivec) -> - PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, CipherBin), - {ok, PlainBin, crypto:next_iv(aes_cbc,CipherBin), DataSize} - end, - - Cendw = fun(PlainBin, _) when PlainBin == <<>> -> {ok, <<>>}; - (PlainBin, Ivec) -> - CipherBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, - pad(16,PlainBin)), %% Last chunk - {ok, CipherBin} - end, +block_size_16_crypto_tar(Config) -> cipher_crypto_tar(aes_256_cbc, Config). +block_size_1_crypto_tar(Config) -> cipher_crypto_tar(aes_256_ctr, Config). - Cw = {Cinitw,Cenc,Cendw}, - TarFileName = proplists:get_value(tar_filename, Config), - SftpTarFileName = w2l(Config, TarFileName), - - {ok,HandleWrite} = ssh_sftp:open_tar(ChPid2, SftpTarFileName, [write,{crypto,Cw}]), - [ok = erl_tar:add(HandleWrite, Bin, Name, [verbose]) || {Name,Bin} <- NameBins], - ok = erl_tar:close(HandleWrite), - - Cr = {Cinitr,Cdec}, - chk_tar(NameBins, Config, [{crypto,Cr}]). - - -pad(BlockSize, Bin) -> - PadSize = (BlockSize - (size(Bin) rem BlockSize)) rem BlockSize, - list_to_binary( lists:duplicate(PadSize,0) ). - -%%-------------------------------------------------------------------- -aes_ctr_stream_crypto_tar(Config) -> +cipher_crypto_tar(Cipher, Config) -> ChPid2 = proplists:get_value(channel_pid2, Config), NameBins = lists:sort( [{"b1",<<"A binary">>}, @@ -1074,22 +1030,25 @@ aes_ctr_stream_crypto_tar(Config) -> Key = <<"This is a 256 bit key. Boring...">>, Ivec0 = crypto:strong_rand_bytes(16), - Cinitw = Cinitr = fun() -> {ok, crypto:stream_init(aes_ctr,Key,Ivec0)} end, + Cinitw = fun() -> {ok, crypto:crypto_init(Cipher,Key,Ivec0,[{encrypt,true}, + {padding,zero}])} end, + Cinitr = fun() -> {ok, crypto:crypto_init(Cipher,Key,Ivec0,false)} end, Cenc = fun(PlainBin,State) -> - {NewState,CipherBin} = crypto:stream_encrypt(State, PlainBin), - {ok, CipherBin, NewState} + CipherBin = crypto:crypto_update(State, PlainBin), + {ok, CipherBin, State} end, Cdec = fun(CipherBin,State) -> - {NewState,PlainBin} = crypto:stream_decrypt(State, CipherBin), - {ok, PlainBin, NewState} + PlainBin = crypto:crypto_update(State, CipherBin), + {ok, PlainBin, State} end, - Cendw = fun(PlainBin, _) when PlainBin == <<>> -> {ok, <<>>}; - (PlainBin, Ivec) -> - CipherBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, - pad(16,PlainBin)), %% Last chunk - {ok, CipherBin} + Cendw = fun(PlainBin, State) -> + CipherBin1 = crypto:crypto_update(State, PlainBin), + Sz1 = size(CipherBin1), + CipherBin2 = crypto:crypto_final(State), + Sz2 = size(CipherBin2), + {ok, <<CipherBin1:Sz1/binary, CipherBin2:Sz2/binary>>} end, Cw = {Cinitw,Cenc,Cendw}, diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl index 73bfc13eef..ba4f420b1f 100644 --- a/lib/ssh/test/ssh_sup_SUITE.erl +++ b/lib/ssh/test/ssh_sup_SUITE.erl @@ -115,18 +115,21 @@ sshc_subtree(Config) when is_list(Config) -> {user_interaction, false}, {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]), - ?wait_match([{_, _,worker,[ssh_connection_handler]}], - supervisor:which_children(sshc_sup)), + ?wait_match([{{client,ssh_system_sup, LocalIP, LocalPort, ?DEFAULT_PROFILE}, + SysSup, supervisor,[ssh_system_sup]}], + supervisor:which_children(sshc_sup), + [SysSup, LocalIP, LocalPort]), + check_sshc_system_tree(SysSup, Pid1, LocalIP, LocalPort, Config), {ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, {user_interaction, false}, {user, ?USER}, {password, ?PASSWD}, {user_dir, UserDir}]), - ?wait_match([{_,_,worker,[ssh_connection_handler]}, - {_,_,worker,[ssh_connection_handler]}], + ?wait_match([{_, _,supervisor,[ssh_system_sup]}, + {_, _,supervisor,[ssh_system_sup]}], supervisor:which_children(sshc_sup)), ssh:close(Pid1), - ?wait_match([{_,_,worker,[ssh_connection_handler]}], + ?wait_match([{_, _,supervisor,[ssh_system_sup]}], supervisor:which_children(sshc_sup)), ssh:close(Pid2), ?wait_match([], supervisor:which_children(sshc_sup)). @@ -212,6 +215,7 @@ killed_acceptor_restarts(Config) -> ct:log("~s",[lists:flatten(ssh_info:string())]), %% Make acceptor restart: + ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [AccPid]), exit(AccPid, kill), ?wait_match(undefined, process_info(AccPid)), @@ -221,6 +225,8 @@ killed_acceptor_restarts(Config) -> AccPid1, 500, 30), + ct:pal("... now there should not be any SUPERVISOR REPORT.~n", []), + true = (AccPid1 =/= AccPid2), %% Connect second client and check it is alive: @@ -331,12 +337,14 @@ chk_empty_con_daemon(Daemon) -> {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}], supervisor:which_children(Daemon), [SubSysSup,AccSup]), - ?wait_match([{{server,ssh_connection_sup, _,_}, + ?wait_match([{_,_, supervisor, + [ssh_tcpip_forward_acceptor_sup]}, + {{server,ssh_connection_sup, _,_}, ConnectionSup, supervisor, [ssh_connection_sup]}, - {{server,ssh_server_channel_sup,_ ,_}, + {{server,ssh_channel_sup,_ ,_}, ChannelSup,supervisor, - [ssh_server_channel_sup]}], + [ssh_channel_sup]}], supervisor:which_children(SubSysSup), [ConnectionSup,ChannelSup]), ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}], @@ -362,12 +370,15 @@ check_sshd_system_tree(Daemon, Host, Port, Config) -> supervisor:which_children(Daemon), [SubSysSup,AccSup]), - ?wait_match([{{server,ssh_connection_sup, _,_}, + ?wait_match([{_, + _AcceptorSup, supervisor, + [ssh_tcpip_forward_acceptor_sup]}, + {{server,ssh_connection_sup, _,_}, ConnectionSup, supervisor, [ssh_connection_sup]}, - {{server,ssh_server_channel_sup,_ ,_}, + {{server,ssh_channel_sup,_ ,_}, ChannelSup,supervisor, - [ssh_server_channel_sup]}], + [ssh_channel_sup]}], supervisor:which_children(SubSysSup), [ConnectionSup,ChannelSup]), @@ -379,12 +390,58 @@ check_sshd_system_tree(Daemon, Host, Port, Config) -> ?wait_match([], supervisor:which_children(ChannelSup)), - ssh_sftp:start_channel(Client), + {ok,PidC} = ssh_sftp:start_channel(Client), + + ?wait_match([{_, PidS,worker,[ssh_server_channel]}], + supervisor:which_children(ChannelSup), + [PidS]), + true = (PidS =/= PidC), - ?wait_match([{_, _,worker,[ssh_server_channel]}], - supervisor:which_children(ChannelSup)), ssh:close(Client). + +check_sshc_system_tree(SysSup, Connection, LocalIP, LocalPort, _Config) -> + ?wait_match([{_,SubSysSup,supervisor,[ssh_subsystem_sup]}], + supervisor:which_children(SysSup), + [SubSysSup]), + ?wait_match([{_,FwdAccSup, supervisor, + [ssh_tcpip_forward_acceptor_sup]}, + {{client,ssh_connection_sup, LocalIP, LocalPort}, + ConnectionSup, supervisor, + [ssh_connection_sup]}, + {{client,ssh_channel_sup, LocalIP, LocalPort}, + ChannelSup,supervisor, + [ssh_channel_sup]}], + supervisor:which_children(SubSysSup), + [ConnectionSup,ChannelSup,FwdAccSup]), + ?wait_match([{_, Connection, worker,[ssh_connection_handler]}], + supervisor:which_children(ConnectionSup)), + ?wait_match([], supervisor:which_children(ChannelSup)), + ?wait_match([], supervisor:which_children(FwdAccSup)), + + {ok,ChPid1} = ssh_sftp:start_channel(Connection), + ?wait_match([{_,ChPid1,worker,[ssh_client_channel]}], + supervisor:which_children(ChannelSup)), + + {ok,ChPid2} = ssh_sftp:start_channel(Connection), + ?wait_match([{_,ChPidA,worker,[ssh_client_channel]}, + {_,ChPidB,worker,[ssh_client_channel]}], + supervisor:which_children(ChannelSup), + [ChPidA, ChPidB]), + true = (lists:sort([ChPidA, ChPidB]) == lists:sort([ChPid1, ChPid2])), + + ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid1]), + exit(ChPid1, kill), + ?wait_match([{_,ChPid2,worker,[ssh_client_channel]}], + supervisor:which_children(ChannelSup)), + + ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid2]), + exit(ChPid2, kill), + ?wait_match([], supervisor:which_children(ChannelSup)), + ct:pal("... now there should not be any SUPERVISOR REPORT.~n", []). + + + acceptor_pid(DaemonPid) -> Parent = self(), Pid = spawn(fun() -> diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index 83481e6c33..50f63c4096 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -432,7 +432,7 @@ setup_eddsa(Alg, DataDir, UserDir) -> file:copy(filename:join(DataDir, HostPub), filename:join(System, HostPub)), ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), setup_eddsa_known_host(HostPub, DataDir, UserDir), - setup_eddsa_auth_keys(IdPriv, DataDir, UserDir). + setup_eddsa_auth_keys(Alg, DataDir, UserDir). clean_dsa(UserDir) -> del_dirs(filename:join(UserDir, "system")), @@ -572,9 +572,12 @@ setup_ecdsa_auth_keys(Size, Dir, UserDir) -> PKey = #'ECPoint'{point = Q}, setup_auth_keys([{ {PKey,Param}, [{comment, "Test"}]}], UserDir). -setup_eddsa_auth_keys(IdPriv, Dir, UserDir) -> - {ok, Pem} = file:read_file(filename:join(Dir, IdPriv)), - {ed_pri, Alg, Pub, _} = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))), +setup_eddsa_auth_keys(Alg, Dir, UserDir) -> + SshAlg = case Alg of + ed25519 -> 'ssh-ed25519'; + ed448 -> 'ssh-ed448' + end, + {ok, {ed_pri,Alg,Pub,_}} = ssh_file:user_key(SshAlg, [{user_dir,Dir}]), setup_auth_keys([{{ed_pub,Alg,Pub}, [{comment, "Test"}]}], UserDir). setup_auth_keys(Keys, Dir) -> diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index 4f06bd3d65..dc03445b41 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -48,12 +48,20 @@ all() -> end. groups() -> - [{erlang_client, [], [erlang_shell_client_openssh_server + [{erlang_client, [], [tunnel_in_erlclient_erlserver, + tunnel_out_erlclient_erlserver, + {group, tunnel_distro_server}, + erlang_shell_client_openssh_server ]}, - {erlang_server, [], [erlang_server_openssh_client_renegotiate, + {tunnel_distro_server, [], [tunnel_in_erlclient_openssh_server, + tunnel_out_erlclient_openssh_server]}, + {erlang_server, [], [{group, tunnel_distro_client}, + erlang_server_openssh_client_renegotiate, exec_with_io_in_sshc, exec_direct_with_io_in_sshc - ]} + ]}, + {tunnel_distro_client, [], [tunnel_in_non_erlclient_erlserver, + tunnel_out_non_erlclient_erlserver]} ]. init_per_suite(Config) -> @@ -78,6 +86,14 @@ init_per_group(erlang_server, Config) -> ssh_test_lib:setup_dsa_known_host(DataDir, UserDir), ssh_test_lib:setup_rsa_known_host(DataDir, UserDir), Config; +init_per_group(G, Config) when G==tunnel_distro_server ; + G==tunnel_distro_client -> + case no_forwarding() of + true -> + {skip, "port forwarding disabled in external ssh"}; + false -> + Config + end; init_per_group(erlang_client, Config) -> CommonAlgs = ssh_test_lib:algo_intersection( ssh:default_algorithms(), @@ -247,8 +263,184 @@ erlang_server_openssh_client_renegotiate(Config) -> end. %%-------------------------------------------------------------------- +tunnel_out_non_erlclient_erlserver(Config) -> + SystemDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + KnownHosts = filename:join(PrivDir, "known_hosts"), + + {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true}, + {system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + {ToSock, _ToHost, ToPort} = tunneling_listner(), + + ListenHost = {127,0,0,1}, + ListenPort = 2345, + + Cmd = ssh_test_lib:open_sshc_cmd(Host, Port, + [" -o UserKnownHostsFile=", KnownHosts, + " -o StrictHostKeyChecking=no", + " -R ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]), + spawn(fun() -> + ct:log(["ssh command:\r\n ",Cmd],[]), + R = os:cmd(Cmd), + ct:log(["ssh returned:\r\n",R],[]) + end), + + ct:sleep(1000), + test_tunneling(ToSock, ListenHost, ListenPort). + +%%-------------------------------------------------------------------- +tunnel_in_non_erlclient_erlserver(Config) -> + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + KnownHosts = filename:join(UserDir, "known_hosts"), + {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true}, + {system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + {ToSock, _ToHost, ToPort} = tunneling_listner(), + + ListenHost = {127,0,0,1}, + ListenPort = 2345, + + Cmd = + ssh_test_lib:open_sshc_cmd(Host, Port, + [" -o UserKnownHostsFile=", KnownHosts, + " -o StrictHostKeyChecking=no", + " -L ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]), + spawn(fun() -> + ct:log(["ssh command:\r\n ",Cmd],[]), + R = os:cmd(Cmd), + ct:log(["ssh returned:\r\n",R],[]) + end), + ct:sleep(1000), + test_tunneling(ToSock, ListenHost, ListenPort). + +%%-------------------------------------------------------------------- +tunnel_in_erlclient_erlserver(Config) -> + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true}, + {system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"foo", "bar"}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user,"foo"},{password,"bar"}, + {user_interaction, false}]), + {ToSock, ToHost, ToPort} = tunneling_listner(), + + ListenHost = {127,0,0,1}, + {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost,ToPort, 2000), + + test_tunneling(ToSock, ListenHost, ListenPort). + +%%-------------------------------------------------------------------- +tunnel_in_erlclient_openssh_server(_Config) -> + C = ssh_test_lib:connect(loopback, 22, [{silently_accept_hosts, true}, + {user_interaction, false}]), + {ToSock, ToHost, ToPort} = tunneling_listner(), + + ListenHost = {127,0,0,1}, + {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost,ToPort, 5000), + + test_tunneling(ToSock, ListenHost, ListenPort). + +%%-------------------------------------------------------------------- +tunnel_out_erlclient_erlserver(Config) -> + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true}, + {system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"foo", "bar"}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user,"foo"},{password,"bar"}, + {user_interaction, false}]), + {ToSock, ToHost, ToPort} = tunneling_listner(), + + ListenHost = {127,0,0,1}, + {ok,ListenPort} = ssh:tcpip_tunnel_from_server(C, ListenHost,0, ToHost,ToPort, 5000), + + test_tunneling(ToSock, ListenHost, ListenPort). + +%%-------------------------------------------------------------------- +tunnel_out_erlclient_openssh_server(_Config) -> + C = ssh_test_lib:connect(loopback, 22, [{silently_accept_hosts, true}, + {user_interaction, false}]), + {ToSock, ToHost, ToPort} = tunneling_listner(), + + ListenHost = {127,0,0,1}, + {ok,ListenPort} = ssh:tcpip_tunnel_from_server(C, ListenHost,0, ToHost,ToPort, 5000), + + test_tunneling(ToSock, ListenHost, ListenPort). + +%%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- +tunneling_listner() -> + {ok,LSock} = gen_tcp:listen(0, [{active,false}]), + {ok, {LHost,LPort}} = inet:sockname(LSock), + {LSock, LHost, LPort}. + +test_tunneling(ListenSocket, Host, Port) -> + {ok,Client1} = gen_tcp:connect(Host, Port, [{active,false}]), + {ok,Server1} = gen_tcp:accept(ListenSocket), + {ok,Client2} = gen_tcp:connect(Host, Port, [{active,false}]), + {ok,Server2} = gen_tcp:accept(ListenSocket), + send_rcv("Hi!", Client1, Server1), + send_rcv("Happy to see you!", Server1, Client1), + send_rcv("Hi, you to!", Client2, Server2), + send_rcv("Happy to see you also!", Server2, Client2), + close_and_check(Client1, Server1), + send_rcv("Still there?", Client2, Server2), + send_rcv("Yes!", Server2, Client2), + close_and_check(Server2, Client2). + + +tcp_connect(Host, Port, Options) -> + tcp_connect(Host, Port, Options, 0). +tcp_connect(Host, Port, Options, Timeout) -> + ct:log("Try connect to ~p:~p ~p Timeout=~p", [Host, Port, Options, Timeout]), + case gen_tcp:connect(Host, Port, Options, Timeout) of + {error,econnrefused} -> + timer:sleep( 2*max(Timeout,250)), + tcp_connect(Host, Port, Options, 2*max(Timeout,250)); + {error,timeout} -> + timer:sleep( 2*max(Timeout,250)), + tcp_connect(Host, Port, Options, 2*max(Timeout,250)); + {ok,S} -> + ct:log("connect to ~p:~p ~p Timeout=~p -> ~p", [Host, Port, Options, Timeout, S]), + {ok,S} + end. + +close_and_check(OneSide, OtherSide) -> + ok = gen_tcp:close(OneSide), + ok = chk_closed(OtherSide). + + +chk_closed(Sock) -> + chk_closed(Sock, 0). +chk_closed(Sock, Timeout) -> + case gen_tcp:recv(Sock, 0, Timeout) of + {error,closed} -> + ok; + {error,timeout} -> + chk_closed(Sock, 2*max(Timeout,250)); + Other -> + Other + end. + +send_rcv(Txt, From, To) -> + ct:log("Send ~p from ~p to ~p", [Txt, From, To]), + ok = gen_tcp:send(From, Txt), + ct:log("Recv ~p on ~p", [Txt, To]), + {ok,Txt} = gen_tcp:recv(To, 0, 5000), + ok. + +%%-------------------------------------------------------------------- receive_data(Data, Conn) -> receive Info when is_binary(Info) -> @@ -340,3 +532,41 @@ comment(AtomList) -> ct:comment( string:join(lists:map(fun erlang:atom_to_list/1, AtomList), ", ")). + +%%%---------------------------------------------------------------- +no_forwarding() -> + %%% Check if the ssh of the OS has tunneling enabled + Cmnd = "ssh -R 0:localhost:4567 localhost exit", + FailRegExp = + "Port forwarding is disabled" + "|remote port forwarding failed" + "|Bad.*specification" + "|Bad forwarding port", + {Result,TheText} = + try + Parent = self(), + Pid = spawn(fun() -> + Parent ! {self(), os:cmd(Cmnd)} + end), + receive + {Pid, Txt} -> + case re:run(Txt, FailRegExp) of + {match,_} -> {true,Txt}; + _ -> {false,Txt} + end + after 10000 -> + ct:log("*** TIMEOUT ***",[]), + {true,""} + end + catch C:E:S -> + ct:log("Exception in no_forwarding():~n~p:~p~n~p~n", [C,E,S]), + {true, ""} + end, + ct:log("---- os:cmd(~p) returned:~n~s~n" + "~n" + "---- Checking with regexp~n" + "~p~n" + "~n" + "---- The function no_forwarding() returns ~p", + [Cmnd,TheText, FailRegExp, Result]), + Result. |