From f7bae98de902c597d9f1e5861745e90aecc58191 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Feb 2017 14:34:52 +0100 Subject: public_key: generate a list of ssh fingerprints on request --- lib/public_key/doc/src/public_key.xml | 5 +++++ lib/public_key/src/public_key.erl | 26 ++++++++++++++++-------- lib/public_key/test/public_key_SUITE.erl | 34 +++++++++++++++++++++----------- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 37aa05e0fd..c97ec361d1 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -857,6 +857,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, ssh_hostkey_fingerprint(HostKey) -> string() ssh_hostkey_fingerprint(DigestType, HostKey) -> string() + ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()] Calculates a ssh fingerprint for a hostkey. Key = public_key() @@ -880,6 +881,10 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, 5> public_key:ssh_hostkey_fingerprint(sha256,Key). "SHA256:aZGXhabfbf4oxglxltItWeHU7ub3Dc31NcNw2cMJePQ" + + 6> public_key:ssh_hostkey_fingerprint([sha,sha256],Key). + ["SHA1:bSLY/C4QXLDL/Iwmhyg0PGW9UbY", + "SHA256:aZGXhabfbf4oxglxltItWeHU7ub3Dc31NcNw2cMJePQ"] diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 402f514803..adc35073d6 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -893,21 +893,31 @@ oid2ssh_curvename(?'secp521r1') -> <<"nistp521">>. %%-------------------------------------------------------------------- -spec ssh_hostkey_fingerprint(public_key()) -> string(). --spec ssh_hostkey_fingerprint(digest_type(), public_key()) -> string(). +-spec ssh_hostkey_fingerprint( digest_type(), public_key()) -> string() + ; ([digest_type()], public_key()) -> [string()] + . ssh_hostkey_fingerprint(Key) -> - sshfp_string(md5, Key). + sshfp_string(md5, public_key:ssh_encode(Key,ssh2_pubkey) ). + +ssh_hostkey_fingerprint(HashAlgs, Key) when is_list(HashAlgs) -> + EncKey = public_key:ssh_encode(Key, ssh2_pubkey), + [sshfp_full_string(HashAlg,EncKey) || HashAlg <- HashAlgs]; +ssh_hostkey_fingerprint(HashAlg, Key) when is_atom(HashAlg) -> + EncKey = public_key:ssh_encode(Key, ssh2_pubkey), + sshfp_full_string(HashAlg, EncKey). -ssh_hostkey_fingerprint(HashAlg, Key) -> - lists:concat([sshfp_alg_name(HashAlg), - [$: | sshfp_string(HashAlg, Key)] - ]). -sshfp_string(HashAlg, Key) -> +sshfp_string(HashAlg, EncodedKey) -> %% Other HashAlgs than md5 will be printed with %% other formats than hextstr by %% ssh-keygen -E -lf - fp_fmt(sshfp_fmt(HashAlg), crypto:hash(HashAlg, public_key:ssh_encode(Key,ssh2_pubkey))). + fp_fmt(sshfp_fmt(HashAlg), crypto:hash(HashAlg, EncodedKey)). + +sshfp_full_string(HashAlg, EncKey) -> + lists:concat([sshfp_alg_name(HashAlg), + [$: | sshfp_string(HashAlg, EncKey)] + ]). sshfp_alg_name(sha) -> "SHA1"; sshfp_alg_name(Alg) -> string:to_upper(atom_to_list(Alg)). diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 615ff32539..68aa152911 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -54,7 +54,8 @@ all() -> ssh_hostkey_fingerprint_sha, ssh_hostkey_fingerprint_sha256, ssh_hostkey_fingerprint_sha384, - ssh_hostkey_fingerprint_sha512 + ssh_hostkey_fingerprint_sha512, + ssh_hostkey_fingerprint_list ]. groups() -> @@ -93,20 +94,21 @@ end_per_group(_GroupName, Config) -> %%------------------------------------------------------------------- init_per_testcase(TestCase, Config) -> case TestCase of - ssh_hostkey_fingerprint_md5_implicit -> init_fingerprint_testcase(md5, Config); - ssh_hostkey_fingerprint_md5 -> init_fingerprint_testcase(md5, Config); - ssh_hostkey_fingerprint_sha -> init_fingerprint_testcase(sha, Config); - ssh_hostkey_fingerprint_sha256 -> init_fingerprint_testcase(sha256, Config); - ssh_hostkey_fingerprint_sha384 -> init_fingerprint_testcase(sha384, Config); - ssh_hostkey_fingerprint_sha512 -> init_fingerprint_testcase(sha512, Config); + ssh_hostkey_fingerprint_md5_implicit -> init_fingerprint_testcase([md5], Config); + ssh_hostkey_fingerprint_md5 -> init_fingerprint_testcase([md5], Config); + ssh_hostkey_fingerprint_sha -> init_fingerprint_testcase([sha], Config); + ssh_hostkey_fingerprint_sha256 -> init_fingerprint_testcase([sha256], Config); + ssh_hostkey_fingerprint_sha384 -> init_fingerprint_testcase([sha384], Config); + ssh_hostkey_fingerprint_sha512 -> init_fingerprint_testcase([sha512], Config); + ssh_hostkey_fingerprint_list -> init_fingerprint_testcase([sha,md5], Config); _ -> init_common_per_testcase(Config) end. -init_fingerprint_testcase(Alg, Config) -> - CryptoSupports = lists:member(Alg, proplists:get_value(hashs, crypto:supports())), - case CryptoSupports of - false -> {skip,{Alg,not_supported}}; - true -> init_common_per_testcase(Config) +init_fingerprint_testcase(Algs, Config) -> + Hashs = proplists:get_value(hashs, crypto:supports(), []), + case Algs -- Hashs of + [] -> init_common_per_testcase(Config); + UnsupportedAlgs -> {skip,{UnsupportedAlgs,not_supported}} end. init_common_per_testcase(Config0) -> @@ -599,6 +601,14 @@ ssh_hostkey_fingerprint_sha512(_Config) -> Expected = "SHA512:ezUismvm3ADQQb6Nm0c1DwQ6ydInlJNfsnSQejFkXNmABg1Aenk9oi45CXeBOoTnlfTsGG8nFDm0smP10PBEeA", Expected = public_key:ssh_hostkey_fingerprint(sha512, ssh_hostkey(rsa)). +%%-------------------------------------------------------------------- +%% Since this kind of fingerprint is not available yet on standard +%% distros, we do like this instead. +ssh_hostkey_fingerprint_list(_Config) -> + Expected = ["SHA1:Soammnaqg06jrm2jivMSnzQGlmk", + "MD5:4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a"], + Expected = public_key:ssh_hostkey_fingerprint([sha,md5], ssh_hostkey(rsa)). + %%-------------------------------------------------------------------- encrypt_decrypt() -> [{doc, "Test public_key:encrypt_private and public_key:decrypt_public"}]. -- cgit v1.2.3 From 9f23065062eb724e58f39a65e416e5b0e1e9d95d Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Fri, 10 Feb 2017 14:37:41 +0100 Subject: ssh: allow a list of fingerprint algos in silently_accept_hosts option --- lib/ssh/doc/src/ssh.xml | 13 ++++++++++--- lib/ssh/src/ssh.erl | 21 ++++++++++++++++----- lib/ssh/test/ssh_options_SUITE.erl | 26 +++++++++++++++++++------- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 6b49f89449..1a6bac8355 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -175,9 +175,11 @@ supplied with this option.

- +
- boolean()]]> + +
+ boolean()]]>

When true, hosts are added to the @@ -188,8 +190,13 @@ (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 +

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

+

If the HashAlgoSpec is present and is a list of crypto:digest_type(), the fingerprint is calulated for + each digest_type and PeerHostKeyFingerPrint is the list of the results in order corresponding to the + HashAlgoSpec.

diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 31e343e81b..f408086c0f 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -620,11 +620,22 @@ handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) - 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}}) + Algs = if is_atom(DigestAlg) -> [DigestAlg]; + is_list(DigestAlg) -> DigestAlg; + true -> throw({error, {eoptions, Opt}}) + end, + case [A || A <- Algs, + not lists:member(A, [md5, sha, sha224, sha256, sha384, sha512])] of + [_|_] = UnSup1 -> + throw({error, {{eoptions, Opt}, {not_fingerprint_algos,UnSup1}}}); + [] -> + CryptoHashAlgs = proplists:get_value(hashs, crypto:supports(), []), + case [A || A <- Algs, + not lists:member(A, CryptoHashAlgs)] of + [_|_] = UnSup2 -> + throw({error, {{eoptions, Opt}, {unsupported_algo,UnSup2}}}); + [] -> Opt + end end; handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) -> Opt; diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 86f5cb1746..d07c596411 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -67,7 +67,8 @@ hostkey_fingerprint_check_sha/1, hostkey_fingerprint_check_sha256/1, hostkey_fingerprint_check_sha384/1, - hostkey_fingerprint_check_sha512/1 + hostkey_fingerprint_check_sha512/1, + hostkey_fingerprint_check_list/1 ]). %%% Common test callbacks @@ -112,6 +113,7 @@ all() -> hostkey_fingerprint_check_sha256, hostkey_fingerprint_check_sha384, hostkey_fingerprint_check_sha512, + hostkey_fingerprint_check_list, id_string_no_opt_client, id_string_own_string_client, id_string_random_client, @@ -812,6 +814,8 @@ hostkey_fingerprint_check_sha384(Config) -> hostkey_fingerprint_check_sha512(Config) -> do_hostkey_fingerprint_check(Config, sha512). +hostkey_fingerprint_check_list(Config) -> + do_hostkey_fingerprint_check(Config, [sha,md5,sha256]). %%%---- do_hostkey_fingerprint_check(Config, HashAlg) -> @@ -824,9 +828,10 @@ do_hostkey_fingerprint_check(Config, HashAlg) -> supported_hash(old) -> true; supported_hash(HashAlg) -> - proplists:get_value(HashAlg, - proplists:get_value(hashs, crypto:supports(), []), - false). + Hs = if is_atom(HashAlg) -> [HashAlg]; + is_list(HashAlg) -> HashAlg + end, + [] == (Hs -- proplists:get_value(hashs, crypto:supports(), [])). really_do_hostkey_fingerprint_check(Config, HashAlg) -> @@ -840,7 +845,7 @@ really_do_hostkey_fingerprint_check(Config, HashAlg) -> %% 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 + FPs0 = [case HashAlg of old -> public_key:ssh_hostkey_fingerprint(Key); _ -> public_key:ssh_hostkey_fingerprint(HashAlg, Key) end @@ -856,6 +861,9 @@ really_do_hostkey_fingerprint_check(Config, HashAlg) -> _:_ -> [] end end], + FPs = if is_atom(HashAlg) -> FPs0; + is_list(HashAlg) -> lists:concat(FPs0) + end, ct:log("Fingerprints(~p) = ~p",[HashAlg,FPs]), %% Start daemon with the public keys that we got fingerprints from @@ -866,8 +874,12 @@ really_do_hostkey_fingerprint_check(Config, HashAlg) -> 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", + FPCheck = + if is_atom(HashAlg) -> lists:member(FP, FPs); + is_list(HashAlg) -> lists:all(fun(FP1) -> lists:member(FP1,FPs) end, + FP) + end, + ct:log("check ~p == ~p (~p) and ~n~p~n in ~p (~p)~n", [PeerName,Host,HostCheck,FP,FPs,FPCheck]), HostCheck and FPCheck end, -- cgit v1.2.3 From 82a5b5f3b8824ab7c1da403c3a40bcf0fc98c690 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 13:26:18 +0100 Subject: ssh: reword documentation --- lib/ssh/doc/src/ssh.xml | 61 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 1a6bac8355..e42f16ebd0 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -153,7 +153,7 @@

IP version to use.

- +

Sets the user directory, that is, the directory containing ssh configuration files for the user, such as @@ -175,29 +175,48 @@ supplied with this option.

- -
- -
- boolean()]]> + +
+
+
+
+
+ boolean()]]>
+
+
-

When true, hosts are added to the - file without asking the user. - 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 HashAlgoSpec is present and is an crypto:digest_type(), the fingerprint is calculated - with that digest type by the function - public_key:ssh_hostkey_fingerprint/2. +

This option guides the connect function how to act when the connected server presents a Host + Key that the client has not seen before. The default is to ask the user with a question on stdio of whether to + accept or reject the new Host Key. + See also the option user_dir + for the path to the file known_hosts where previously accepted Host Keys are recorded.

-

If the HashAlgoSpec is present and is a list of crypto:digest_type(), the fingerprint is calulated for - each digest_type and PeerHostKeyFingerPrint is the list of the results in order corresponding to the - HashAlgoSpec. -

+

The option can be given in three different forms as seen above:

+ + The value is a boolean(). The value true will make the client accept any unknown + Host Key without any user interaction. The value false keeps the default behaviour of asking the + the user on stdio. + + A CallbackFun will be called and the boolean return value true will make the client + accept the Host Key. A reurn value of false will make the client to reject the Host Key and therefore + also the connection will be closed. The arguments to the fun are: + + PeerName - a string with the name or address of the remote host. + FingerPrint - the fingerprint of the Host Key as + public_key:ssh_hostkey_fingerprint/1 + calculates it. + + + + A tuple {HashAlgoSpec, CallbackFun}. The HashAlgoSpec specifies which hash algorithm + shall be used to calculate the fingerprint used in the call of the CallbackFun. The HashALgoSpec + is either an atom or a list of atoms as the first argument in + public_key:ssh_hostkey_fingerprint/2. + If it is a list of hash algorithm names, the FingerPrint argument in the CallbackFun will be + a list of fingerprints in the same order as the corresponding name in the HashAlgoSpec list. + +
-- cgit v1.2.3 From 2869472d38814d8ab5f034e383c7aa063aab4618 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 13:29:32 +0100 Subject: ssh: speling error --- lib/ssh/doc/src/ssh.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index e42f16ebd0..20508a73a6 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -226,7 +226,7 @@ supplying a password. Defaults to true. Even if user interaction is allowed it can be suppressed by other options, such as silently_accept_hosts - and password. However, those optins are not always desirable + and password. However, those options are not always desirable to use from a security point of view.

-- cgit v1.2.3 From 5fa9ae3f7cce55047061b94f35940d6eaf94d9ee Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Wed, 15 Feb 2017 13:34:16 +0100 Subject: ssh: speling error --- lib/ssh/doc/src/ssh.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 20508a73a6..f6e26f5ee8 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -199,7 +199,7 @@ the user on stdio. A CallbackFun will be called and the boolean return value true will make the client - accept the Host Key. A reurn value of false will make the client to reject the Host Key and therefore + accept the Host Key. A return value of false will make the client to reject the Host Key and therefore also the connection will be closed. The arguments to the fun are: PeerName - a string with the name or address of the remote host. -- cgit v1.2.3