summaryrefslogtreecommitdiff
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/Makefile3
-rw-r--r--lib/ssh/doc/src/Makefile87
-rw-r--r--lib/ssh/doc/src/ssh.xml70
-rw-r--r--lib/ssh/src/Makefile8
-rw-r--r--lib/ssh/src/ssh.app.src6
-rw-r--r--lib/ssh/src/ssh.erl171
-rw-r--r--lib/ssh/src/ssh.hrl6
-rw-r--r--lib/ssh/src/ssh_auth.erl8
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl90
-rw-r--r--lib/ssh/src/ssh_connection.erl247
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl221
-rw-r--r--lib/ssh/src/ssh_file.erl694
-rw-r--r--lib/ssh/src/ssh_info.erl35
-rw-r--r--lib/ssh/src/ssh_message.erl150
-rw-r--r--lib/ssh/src/ssh_options.erl12
-rw-r--r--lib/ssh/src/ssh_sftp.erl8
-rw-r--r--lib/ssh/src/ssh_sftpd.erl5
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl48
-rw-r--r--lib/ssh/src/ssh_system_sup.erl27
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_acceptor.erl116
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl (renamed from lib/ssh/src/ssh_server_channel_sup.erl)44
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_client.erl84
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_srv.erl75
-rw-r--r--lib/ssh/src/ssh_transport.erl78
-rw-r--r--lib/ssh/src/ssh_xfer.erl23
-rw-r--r--lib/ssh/src/sshc_sup.erl46
-rw-r--r--lib/ssh/src/sshd_sup.erl2
-rw-r--r--lib/ssh/test/Makefile1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_encode_decode.erl2
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl79
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE.erl269
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa21
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa9
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed255197
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key21
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key9
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa12
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa5
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key12
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key5
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa15
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa8
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa30
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key12
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key5
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl75
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl85
-rw-r--r--lib/ssh/test/ssh_test_lib.erl11
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl236
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.