Skip to content

Commit

Permalink
Add support for key files protected by pass phrases
Browse files Browse the repository at this point in the history
  • Loading branch information
IngelaAndin committed Feb 15, 2012
1 parent 4a9cf8d commit cd667da
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 24 deletions.
14 changes: 13 additions & 1 deletion lib/ssh/doc/src/ssh.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,26 @@
by calling ssh_connect:session_channel/2.</p>
<p>Options are:</p>
<taglist>
<tag><c><![CDATA[{user_dir, String}]]></c></tag>
<tag><c><![CDATA[{user_dir, string()}]]></c></tag>
<item>
<p>Sets the user directory e.i. the directory containing
ssh configuration files for the user such as
<c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa, id_dsa]]></c> and
<c><![CDATA[authorized_key]]></c>. Defaults to the directory normally
referred to as <c><![CDATA[~/.ssh]]></c> </p>
</item>
<tag><c><![CDATA[{dsa_pass_phrase, string()}]]></c></tag>
<item>
<p>If the user dsa key is protected by a pass phrase it can be
supplied with this option.
</p>
</item>
<tag><c><![CDATA[{rsa_pass_phrase, string()}]]></c></tag>
<item>
<p>If the user rsa key is protected by a pass phrase it can be
supplied with this option.
</p>
</item>
<tag><c><![CDATA[{silently_accept_hosts, boolean()}]]></c></tag>
<item>
<p>When true hosts are added to the
Expand Down
4 changes: 4 additions & 0 deletions lib/ssh/src/ssh.erl
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ handle_options([{connect_timeout, _} = Opt | Rest], SockOpts, Opts) ->
handle_options(Rest, SockOpts, [Opt | Opts]);
handle_options([{user, _} = Opt | Rest], SockOpts, Opts) ->
handle_options(Rest, SockOpts, [Opt | Opts]);
handle_options([{dsa_pass_phrase, _} = Opt | Rest], SockOpts, Opts) ->
handle_options(Rest, SockOpts, [Opt | Opts]);
handle_options([{rsa_pass_phrase, _} = Opt | Rest], SockOpts, Opts) ->
handle_options(Rest, SockOpts, [Opt | Opts]);
handle_options([{password, _} = Opt | Rest], SockOpts, Opts) ->
handle_options(Rest, SockOpts, [Opt | Opts]);
handle_options([{user_passwords, _} = Opt | Rest], SockOpts, Opts) ->
Expand Down
28 changes: 20 additions & 8 deletions lib/ssh/src/ssh_file.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
%% Used by server
host_key(Algorithm, Opts) ->
File = file_name(system, file_base_name(Algorithm), Opts),
Password = proplists:get_value(password, Opts, ignore),
%% 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),
decode(File, Password).


Expand All @@ -68,9 +71,9 @@ is_host_key(Key, PeerName, Algorithm, Opts) ->
false
end.

user_key(Alg, Opts) ->
File = file_name(user, identity_key_filename(Alg), Opts),
Password = proplists:get_value(password, Opts, ignore),
user_key(Algorithm, Opts) ->
File = file_name(user, identity_key_filename(Algorithm), Opts),
Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
decode(File, Password).


Expand Down Expand Up @@ -210,10 +213,20 @@ do_lookup_host_key(Host, Alg, Opts) ->
Error -> Error
end.

identity_key_filename("ssh-dss") -> "id_dsa";
identity_key_filename("ssh-rsa") -> "id_rsa".
identity_key_filename("ssh-dss") ->
"id_dsa";
identity_key_filename("ssh-rsa") ->
"id_rsa".

identity_pass_phrase("ssh-dss") ->
dsa_pass_phrase;
identity_pass_phrase('ssh-dss') ->
dsa_pass_phrase;
identity_pass_phrase('ssh-rsa') ->
rsa_pass_phrase;
identity_pass_phrase("ssh-rsa") ->
rsa_pass_phrase.


lookup_host_key_fd(Fd, Host, KeyType) ->
case io:get_line(Fd, '') of
eof ->
Expand Down Expand Up @@ -290,7 +303,6 @@ is_auth_key(Key, Key) ->
is_auth_key(_,_) ->
false.


default_user_dir()->
{ok,[[Home|_]]} = init:get_argument(home),
UserDir = filename:join(Home, ".ssh"),
Expand Down
47 changes: 46 additions & 1 deletion lib/ssh/test/ssh_basic_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,16 @@ all() ->
[app_test,
{group, dsa_key},
{group, rsa_key},
{group, dsa_pass_key},
{group, rsa_pass_key},
daemon_already_started,
server_password_option, server_userpassword_option].

groups() ->
[{dsa_key, [], [exec, exec_compressed, shell, known_hosts]},
{rsa_key, [], [exec, exec_compressed, shell, known_hosts]}
{rsa_key, [], [exec, exec_compressed, shell, known_hosts]},
{dsa_pass_key, [], [pass_phrase]},
{rsa_pass_key, [], [pass_phrase]}
].

init_per_group(dsa_key, Config) ->
Expand All @@ -124,6 +128,16 @@ init_per_group(rsa_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:setup_rsa(DataDir, PrivDir),
Config;
init_per_group(rsa_pass_key, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:setup_rsa_pass_pharse(DataDir, PrivDir, "Password"),
[{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
init_per_group(dsa_pass_key, Config) ->
DataDir = ?config(data_dir, 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(_, Config) ->
Config.

Expand All @@ -135,6 +149,14 @@ end_per_group(rsa_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_rsa(PrivDir),
Config;
end_per_group(dsa_pass_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(PrivDir),
Config;
end_per_group(rsa_pass_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_rsa(PrivDir),
Config;
end_per_group(_, Config) ->
Config.

Expand Down Expand Up @@ -424,6 +446,29 @@ known_hosts(Config) when is_list(Config) ->
"ssh-" ++ _ = Alg,
ssh:stop_daemon(Pid).

pass_phrase(doc) ->
["Test that we can use keyes protected by pass phrases"];

pass_phrase(suite) ->
[];

pass_phrase(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
PhraseArg = ?config(pass_phrase, Config),

{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
PhraseArg,
{user_dir, UserDir},
{user_interaction, false}]),
{ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
27 changes: 13 additions & 14 deletions lib/ssh/test/ssh_basic_SUITE_data/id_rsa
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
+SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
-----END RSA PRIVATE KEY-----

29 changes: 29 additions & 0 deletions lib/ssh/test/ssh_test_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,35 @@ clean_rsa(UserDir) ->
file:delete(filename:join(UserDir,"known_hosts")),
file:delete(filename:join(UserDir,"authorized_keys")).

setup_dsa_pass_pharse(DataDir, UserDir, Phrase) ->
{ok, KeyBin} = file:read_file(filename:join(DataDir, "id_dsa")),
setup_pass_pharse(KeyBin, filename:join(UserDir, "id_dsa"), Phrase),
System = filename:join(UserDir, "system"),
file:make_dir(System),
file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")),
file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")),
setup_dsa_known_host(DataDir, UserDir),
setup_dsa_auth_keys(DataDir, UserDir).

setup_rsa_pass_pharse(DataDir, UserDir, Phrase) ->
{ok, KeyBin} = file:read_file(filename:join(DataDir, "id_rsa")),
setup_pass_pharse(KeyBin, filename:join(UserDir, "id_rsa"), Phrase),
System = filename:join(UserDir, "system"),
file:make_dir(System),
file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")),
file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
setup_rsa_known_host(DataDir, UserDir),
setup_rsa_auth_keys(DataDir, UserDir).

setup_pass_pharse(KeyBin, OutFile, Phrase) ->
[{KeyType, _,_} = Entry0] = public_key:pem_decode(KeyBin),
Key = public_key:pem_entry_decode(Entry0),
Salt = crypto:rand_bytes(8),
Entry = public_key:pem_entry_encode(KeyType, Key,
{{"DES-CBC", Salt}, Phrase}),
Pem = public_key:pem_encode([Entry]),
file:write_file(OutFile, Pem).

setup_dsa_known_host(SystemDir, UserDir) ->
{ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_dsa_key.pub")),
[{Key, _}] = public_key:ssh_decode(SshBin, public_key),
Expand Down

0 comments on commit cd667da

Please sign in to comment.