aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVipin Nair <[email protected]>2015-11-13 18:51:03 +0530
committerVipin Nair <[email protected]>2015-12-01 22:56:03 +0530
commite14b301be3b5593a13e666885ca795e6bee54b5b (patch)
tree988f0b65a39d599e19bd10cf6aec9af042f4b408
parent417101bca30b5ae246b97485c7898c4e5c663b9c (diff)
downloadotp-e14b301be3b5593a13e666885ca795e6bee54b5b.tar.gz
otp-e14b301be3b5593a13e666885ca795e6bee54b5b.tar.bz2
otp-e14b301be3b5593a13e666885ca795e6bee54b5b.zip
Support SSH key callback module options
This patch allows extra callback options to be passed to the module implementing the SSH callback module behaviour. A module implementing the SSH key callback API is used to customize the handling of public key. This patch allows extra callback options to be passed to the module implementing the SSH callback module behaviour. The key_cb option has been changed: {key_cb, atom()} -> {key_cb, key_cb()} Where: key_cb() :: atom() | {atom(), list()} The callback options, if specified, is made available to the callback module via the options passed to it under the key 'key_cb_private'. More details and some backgorund is available here[1]. [1]: http://erlang.org/pipermail/erlang-patches/2015-November/004800.html
-rw-r--r--lib/ssh/doc/src/ssh.xml29
-rw-r--r--lib/ssh/src/ssh.erl14
-rw-r--r--lib/ssh/test/Makefile2
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl67
-rw-r--r--lib/ssh/test/ssh_key_cb.erl45
-rw-r--r--lib/ssh/test/ssh_key_cb_options.erl44
6 files changed, 189 insertions, 12 deletions
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 1e9acf4a99..18bced2d1d 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -85,6 +85,15 @@
<item><p><c>atom()</c> - Name of the Erlang module
implementing the subsystem using the <c>ssh_channel</c> behavior, see
<seealso marker="ssh_channel">ssh_channel(3)</seealso></p></item>
+ <tag><c>key_cb() =</c></tag>
+ <item>
+ <p><c>atom() | {atom(), list()}</c></p>
+ <p><c>atom()</c> - Name of the erlang module implementing the behaviours
+ <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> or
+ <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> as the
+ case maybe.</p>
+ <p><c>list()</c> - List of options that can be passed to the callback module.</p>
+ </item>
<tag><c>channel_init_args() =</c></tag>
<item><p><c>list()</c></p></item>
@@ -272,11 +281,13 @@ kex is implicit but public_key is set explicitly.</p>
password, if the password authentication method is
attempted.</p>
</item>
- <tag><c><![CDATA[{key_cb, atom()}]]></c></tag>
+ <tag><c><![CDATA[{key_cb, key_cb()}]]></c></tag>
<item>
- <p>Module implementing the behaviour
- <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>.
- Can be used to customize the handling of public keys.
+ <p>Module implementing the behaviour <seealso
+ marker="ssh_client_key_api">ssh_client_key_api</seealso>. Can be used to
+ customize the handling of public keys. If callback options are provided
+ along with the module name, they are made available to the callback
+ module via the options passed to it under the key 'key_cb_private'.
</p>
</item>
<tag><c><![CDATA[{quiet_mode, atom() = boolean()}]]></c></tag>
@@ -607,11 +618,13 @@ kex is implicit but public_key is set explicitly.</p>
</p>
</item>
- <tag><c><![CDATA[{key_cb, atom()}]]></c></tag>
+ <tag><c><![CDATA[{key_cb, key_cb()}]]></c></tag>
<item>
- <p>Module implementing the behaviour
- <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
- Can be used to customize the handling of public keys.
+ <p>Module implementing the behaviour <seealso
+ marker="ssh_server_key_api">ssh_server_key_api</seealso>. Can be used to
+ customize the handling of public keys. If callback options are provided
+ along with the module name, they are made available to the callback
+ module via the options passed to it under the key 'key_cb_private'.
</p>
</item>
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index bb50e436a3..1d29c95229 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -369,8 +369,12 @@ handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{key_cb, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{key_cb, {Module, Options}} | Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option({key_cb, Module}),
+ handle_ssh_priv_option({key_cb_private, Options}) |
+ SshOptions]);
+handle_option([{key_cb, Module} | Rest], SocketOptions, SshOptions) ->
+ handle_option([{key_cb, {Module, []}} | Rest], SocketOptions, SshOptions);
handle_option([{keyboard_interact_fun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
%%Backwards compatibility
@@ -544,6 +548,9 @@ handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,4) ->
Opt;
handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) ->
Opt;
+handle_ssh_option({key_cb, {CallbackMod, CallbackOptions}} = Opt) when is_atom(CallbackMod),
+ is_list(CallbackOptions) ->
+ Opt;
handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3) ->
Opt;
handle_ssh_option({compression, Value} = Opt) when is_atom(Value) ->
@@ -610,6 +617,9 @@ handle_ssh_option({profile, Value} = Opt) when is_atom(Value) ->
handle_ssh_option(Opt) ->
throw({error, {eoptions, Opt}}).
+handle_ssh_priv_option({key_cb_private, Value} = Opt) when is_list(Value) ->
+ Opt.
+
handle_inet_option({active, _} = Opt) ->
throw({error, {{eoptions, Opt}, "SSH has built in flow control, "
"and active is handled internally, user is not allowed"
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 96c74c6c8a..781a876723 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -47,6 +47,8 @@ MODULES= \
ssh_to_openssh_SUITE \
ssh_upgrade_SUITE \
ssh_test_lib \
+ ssh_key_cb \
+ ssh_key_cb_options \
ssh_trpt_test_lib \
ssh_echo_server \
ssh_peername_sockname_server \
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 0a5964c560..d4cb03f2f2 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -54,8 +54,10 @@
send/1,
shell/1,
shell_no_unicode/1,
- shell_unicode_string/1,
- ssh_info_print/1
+ shell_unicode_string/1,
+ ssh_info_print/1,
+ key_callback/1,
+ key_callback_options/1
]).
%%% Common test callbacks
@@ -84,6 +86,7 @@ all() ->
{group, ecdsa_sha2_nistp521_key},
{group, dsa_pass_key},
{group, rsa_pass_key},
+ {group, key_cb},
{group, internal_error},
daemon_already_started,
double_close,
@@ -101,6 +104,7 @@ groups() ->
{ecdsa_sha2_nistp521_key, [], basic_tests()},
{dsa_pass_key, [], [pass_phrase]},
{rsa_pass_key, [], [pass_phrase]},
+ {key_cb, [], [key_callback, key_callback_options]},
{internal_error, [], [internal_error]}
].
@@ -180,6 +184,11 @@ init_per_group(dsa_pass_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:setup_dsa_pass_pharse(DataDir, PrivDir, "Password"),
[{pass_phrase, {dsa_pass_phrase, "Password"}}| Config];
+init_per_group(key_cb, Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ Config;
init_per_group(internal_error, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
@@ -247,6 +256,10 @@ end_per_group(rsa_pass_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_rsa(PrivDir),
Config;
+end_per_group(key_cb, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ ssh_test_lib:clean_dsa(PrivDir),
+ Config;
end_per_group(internal_error, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(PrivDir),
@@ -575,6 +588,56 @@ pass_phrase(Config) when is_list(Config) ->
{ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
ssh:stop_daemon(Pid).
+%%--------------------------------------------------------------------
+%%% Test that we can use key callback
+key_callback(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(?config(priv_dir, Config), system),
+ UserDir = ?config(priv_dir, Config),
+ NoPubKeyDir = filename:join(UserDir, "nopubkey"),
+ file:make_dir(NoPubKeyDir),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ ConnectOpts = [{silently_accept_hosts, true},
+ {user_dir, NoPubKeyDir},
+ {user_interaction, false},
+ {key_cb, ssh_key_cb}],
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts),
+
+ {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ssh:stop_daemon(Pid).
+
+
+%%--------------------------------------------------------------------
+%%% Test that we can use key callback with callback options
+key_callback_options(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(?config(priv_dir, Config), system),
+ UserDir = ?config(priv_dir, Config),
+
+ NoPubKeyDir = filename:join(UserDir, "nopubkey"),
+ file:make_dir(NoPubKeyDir),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ {ok, PrivKey} = file:read_file(filename:join(UserDir, "id_dsa")),
+
+ ConnectOpts = [{silently_accept_hosts, true},
+ {user_dir, NoPubKeyDir},
+ {user_interaction, false},
+ {key_cb, {ssh_key_cb_options, [{priv_key, PrivKey}]}}],
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts),
+
+ {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ssh:stop_daemon(Pid).
+
%%--------------------------------------------------------------------
%%% Test that client does not hang if disconnects due to internal error
diff --git a/lib/ssh/test/ssh_key_cb.erl b/lib/ssh/test/ssh_key_cb.erl
new file mode 100644
index 0000000000..388ec2ecc1
--- /dev/null
+++ b/lib/ssh/test/ssh_key_cb.erl
@@ -0,0 +1,45 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+
+%% Note: This module is used by ssh_basic_SUITE
+
+-module(ssh_key_cb).
+-behaviour(ssh_client_key_api).
+-compile(export_all).
+
+add_host_key(_, _, _) ->
+ ok.
+
+is_host_key(_, _, _, _) ->
+ true.
+
+user_key('ssh-dss', Opts) ->
+ UserDir = proplists:get_value(user_dir, Opts),
+ KeyFile = filename:join(filename:dirname(UserDir), "id_dsa"),
+ {ok, KeyBin} = file:read_file(KeyFile),
+ [Entry] = public_key:pem_decode(KeyBin),
+ Key = public_key:pem_entry_decode(Entry),
+ {ok, Key};
+
+user_key(_Alg, _Opt) ->
+ {error, "Not Supported"}.
diff --git a/lib/ssh/test/ssh_key_cb_options.erl b/lib/ssh/test/ssh_key_cb_options.erl
new file mode 100644
index 0000000000..afccb34f0f
--- /dev/null
+++ b/lib/ssh/test/ssh_key_cb_options.erl
@@ -0,0 +1,44 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+
+%% Note: This module is used by ssh_basic_SUITE
+
+-module(ssh_key_cb_options).
+-behaviour(ssh_client_key_api).
+-compile(export_all).
+
+add_host_key(_, _, _) ->
+ ok.
+
+is_host_key(_, _, _, _) ->
+ true.
+
+user_key('ssh-dss', Opts) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Opts),
+ KeyBin = proplists:get_value(priv_key, KeyCbOpts),
+ [Entry] = public_key:pem_decode(KeyBin),
+ Key = public_key:pem_entry_decode(Entry),
+ {ok, Key};
+
+user_key(_Alg, _Opt) ->
+ {error, "Not Supported"}.