From 8215ea28fa2f699499b64d6f2c712e068b199390 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 11 Nov 2016 16:59:08 +0100 Subject: ssh: Add fun and fingerprint to option 'silently_accept_host' --- lib/ssh/doc/src/ssh.xml | 14 ++++++++++++-- lib/ssh/src/ssh.erl | 9 +++++++++ lib/ssh/src/ssh_transport.erl | 16 ++++++++++------ 3 files changed, 31 insertions(+), 8 deletions(-) (limited to 'lib/ssh') diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index ef9f7cbd9b..6b49f89449 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -175,11 +175,21 @@ supplied with this option.

- + +
+ boolean()]]> +

When true, hosts are added to the file without asking the user. - Defaults to false. + Defaults to false which will give a user question on stdio of whether to accept or reject a previously + unseen host.

+

If the option value is has an accept_fun(), that fun will called with the arguments + (PeerName, PeerHostKeyFingerPrint). The fingerprint is calculated on the Peer's Host Key with + public_key:ssh_hostkey_fingerprint/1. +

+

If the crypto:digest_type() is present, the fingerprint is calculated with that digest type by the function + public_key:ssh_hostkey_fingerprint/2.

diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 1d7be3547b..31e343e81b 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -617,6 +617,15 @@ handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) -> Opt; +handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_function(Value,2) -> + Opt; +handle_ssh_option({silently_accept_hosts, {DigestAlg,Value}} = Opt) when is_function(Value,2) -> + case lists:member(DigestAlg, [md5, sha, sha224, sha256, sha384, sha512]) of + true -> + Opt; + false -> + throw({error, {eoptions, Opt}}) + end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; handle_ssh_option({preferred_algorithms,[_|_]} = Opt) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 15b80de30a..21ba34506a 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -734,12 +734,16 @@ public_algo({#'ECPoint'{},{namedCurve,OID}}) -> list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)). -accepted_host(Ssh, PeerName, Opts) -> +accepted_host(Ssh, PeerName, Public, Opts) -> case proplists:get_value(silently_accept_hosts, Opts, false) of + F when is_function(F,2) -> + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public))); + {DigestAlg,F} when is_function(F,2) -> + true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public))); true -> - yes; + true; false -> - yes_no(Ssh, "New host " ++ PeerName ++ " accept") + yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept") end. known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, @@ -749,10 +753,10 @@ known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh, true -> ok; false -> - case accepted_host(Ssh, PeerName, Opts) of - yes -> + case accepted_host(Ssh, PeerName, Public, Opts) of + true -> Mod:add_host_key(PeerName, Public, Opts); - no -> + false -> {error, rejected} end end. -- cgit v1.2.3 From e4a22216787f652ecf9044f89dfbbfedd3ddb317 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 14 Nov 2016 15:08:35 +0100 Subject: ssh: Removed irrelevant rfc reference in doc The rfc 4255 is about fingerprints, but only in the context of dns. Since this is out-of-scope for the Erlang/OTP ssh, the reference is missleading. --- lib/ssh/doc/src/introduction.xml | 2 -- lib/ssh/doc/src/ssh_protocol.xml | 2 -- 2 files changed, 4 deletions(-) (limited to 'lib/ssh') diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml index ca84528f3d..b7a73e2597 100644 --- a/lib/ssh/doc/src/introduction.xml +++ b/lib/ssh/doc/src/introduction.xml @@ -195,8 +195,6 @@ Transport Layer Protocol RFC 4254 - Connection Protocol - RFC 4255 - - Key Fingerprints RFC 4344 - Transport Layer Encryption Modes RFC 4716 - diff --git a/lib/ssh/doc/src/ssh_protocol.xml b/lib/ssh/doc/src/ssh_protocol.xml index 7288266cf7..013823b4df 100644 --- a/lib/ssh/doc/src/ssh_protocol.xml +++ b/lib/ssh/doc/src/ssh_protocol.xml @@ -138,8 +138,6 @@ Transport Layer Protocol. RFC 4254 - Connection Protocol. - RFC 4255 - - Key Fingerprints. RFC 4344 - Transport Layer Encryption Modes. RFC 4716 - -- cgit v1.2.3 From 2a98b4a2c29b2e2996a2f5095a824c4ab12e2a0b Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Thu, 17 Nov 2016 18:44:53 +0100 Subject: ssh: added two test cases --- lib/ssh/test/ssh_options_SUITE.erl | 101 ++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) (limited to 'lib/ssh') diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 4cc12cbcbe..8f060bebd8 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -61,7 +61,13 @@ unexpectedfun_option_client/1, unexpectedfun_option_server/1, user_dir_option/1, - connectfun_disconnectfun_server/1 + connectfun_disconnectfun_server/1, + hostkey_fingerprint_check/1, + hostkey_fingerprint_check_md5/1, + hostkey_fingerprint_check_sha/1, + hostkey_fingerprint_check_sha256/1, + hostkey_fingerprint_check_sha384/1, + hostkey_fingerprint_check_sha512/1 ]). %%% Common test callbacks @@ -100,6 +106,12 @@ all() -> disconnectfun_option_client, unexpectedfun_option_server, unexpectedfun_option_client, + hostkey_fingerprint_check, + hostkey_fingerprint_check_md5, + hostkey_fingerprint_check_sha, + hostkey_fingerprint_check_sha256, + hostkey_fingerprint_check_sha384, + hostkey_fingerprint_check_sha512, id_string_no_opt_client, id_string_own_string_client, id_string_random_client, @@ -781,6 +793,93 @@ unexpectedfun_option_client(Config) -> {fail,timeout} end. +%%-------------------------------------------------------------------- +hostkey_fingerprint_check(Config) -> + do_hostkey_fingerprint_check(Config, old). + +hostkey_fingerprint_check_md5(Config) -> + do_hostkey_fingerprint_check(Config, md5). + +hostkey_fingerprint_check_sha(Config) -> + do_hostkey_fingerprint_check(Config, sha). + +hostkey_fingerprint_check_sha256(Config) -> + do_hostkey_fingerprint_check(Config, sha256). + +hostkey_fingerprint_check_sha384(Config) -> + do_hostkey_fingerprint_check(Config, sha384). + +hostkey_fingerprint_check_sha512(Config) -> + do_hostkey_fingerprint_check(Config, sha512). + + +%%%---- +do_hostkey_fingerprint_check(Config, HashAlg) -> + case supported_hash(HashAlg) of + true -> + really_do_hostkey_fingerprint_check(Config, HashAlg); + false -> + {skip,{unsupported_hash,HashAlg}} + end. + +supported_hash(old) -> true; +supported_hash(HashAlg) -> + proplists:get_value(HashAlg, + proplists:get_value(hashs, crypto:supports(), []), + false). + + +really_do_hostkey_fingerprint_check(Config, HashAlg) -> + PrivDir = proplists:get_value(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = proplists:get_value(data_dir, Config), + + %% All host key fingerprints. Trust that public_key has checked the ssh_hostkey_fingerprint + %% function since that function is used by the ssh client... + FPs = [case HashAlg of + old -> public_key:ssh_hostkey_fingerprint(Key); + _ -> public_key:ssh_hostkey_fingerprint(HashAlg, Key) + end + || FileCandidate <- begin + {ok,KeyFileCands} = file:list_dir(SysDir), + KeyFileCands + end, + nomatch =/= re:run(FileCandidate, ".*\\.pub", []), + {Key,_Cmnts} <- begin + {ok,Bin} = file:read_file(filename:join(SysDir, FileCandidate)), + try public_key:ssh_decode(Bin, public_key) + catch + _:_ -> [] + end + end], + ct:log("Fingerprints(~p) = ~p",[HashAlg,FPs]), + + %% Start daemon with the public keys that we got fingerprints from + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}]), + + FP_check_fun = fun(PeerName, FP) -> + ct:pal("PeerName = ~p, FP = ~p",[PeerName,FP]), + HostCheck = (Host == PeerName), + FPCheck = lists:member(FP, FPs), + ct:log("check ~p == ~p (~p) and ~n~p in ~p (~p)~n", + [PeerName,Host,HostCheck,FP,FPs,FPCheck]), + HostCheck and FPCheck + end, + + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, + case HashAlg of + old -> FP_check_fun; + _ -> {HashAlg, FP_check_fun} + end}, + {user, "foo"}, + {password, "morot"}, + {user_dir, UserDir}, + {user_interaction, false}]), + ssh:stop_daemon(Pid). + %%-------------------------------------------------------------------- %%% Test connect_timeout option in ssh:connect/4 ssh_connect_timeout(_Config) -> -- cgit v1.2.3