1%% %CopyrightBegin%
2%%
3%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
4%%
5%% Licensed under the Apache License, Version 2.0 (the "License");
6%% you may not use this file except in compliance with the License.
7%% You may obtain a copy of the License at
8%%
9%%     http://www.apache.org/licenses/LICENSE-2.0
10%%
11%% Unless required by applicable law or agreed to in writing, software
12%% distributed under the License is distributed on an "AS IS" BASIS,
13%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14%% See the License for the specific language governing permissions and
15%% limitations under the License.
16%%
17%% %CopyrightEnd%
18%%
19
20%%
21-module(ssh_pubkey_SUITE).
22
23%% Note: This directive should only be used in test suites.
24-export([
25         suite/0,
26         all/0,
27         groups/0,
28         init_per_suite/1,
29         end_per_suite/1,
30         init_per_group/2,
31         end_per_group/2,
32         init_per_testcase/2,
33         end_per_testcase/2
34        ]).
35
36-export([
37         check_dsa_disabled/1,
38         check_rsa_sha1_disabled/1,
39         connect_dsa_to_dsa/1,
40         connect_dsa_to_ecdsa/1,
41         connect_dsa_to_ed25519/1,
42         connect_dsa_to_ed448/1,
43         connect_dsa_to_rsa_sha2/1,
44         connect_ecdsa_to_dsa/1,
45         connect_ecdsa_to_ecdsa/1,
46         connect_ecdsa_to_ed25519/1,
47         connect_ecdsa_to_ed448/1,
48         connect_ecdsa_to_rsa_sha2/1,
49         connect_ed25519_to_dsa/1,
50         connect_ed25519_to_ecdsa/1,
51         connect_ed25519_to_ed25519/1,
52         connect_ed25519_to_ed448/1,
53         connect_ed25519_to_rsa_sha2/1,
54         connect_ed448_to_dsa/1,
55         connect_ed448_to_ecdsa/1,
56         connect_ed448_to_ed25519/1,
57         connect_ed448_to_ed448/1,
58         connect_ed448_to_rsa_sha2/1,
59         connect_rsa_sha1_to_dsa/1,
60         connect_rsa_sha2_to_dsa/1,
61         connect_rsa_sha2_to_ecdsa/1,
62         connect_rsa_sha2_to_ed25519/1,
63         connect_rsa_sha2_to_ed448/1,
64         connect_rsa_sha2_to_rsa_sha2/1,
65
66         ssh_rsa_public_key/1,
67         ssh_dsa_public_key/1,
68         ssh_ecdsa_public_key/1,
69         ssh_rfc4716_rsa_comment/1,
70         ssh_rfc4716_dsa_comment/1,
71         ssh_rfc4716_rsa_subject/1,
72         ssh_list_public_key/1,
73         ssh_known_hosts/1,
74         ssh1_known_hosts/1,
75         ssh_auth_keys/1,
76         ssh1_auth_keys/1,
77         ssh_openssh_key_with_comment/1,
78         ssh_openssh_key_long_header/1,
79
80         ssh_hostkey_fingerprint_md5_implicit/1,
81         ssh_hostkey_fingerprint_md5/1,
82         ssh_hostkey_fingerprint_sha/1,
83         ssh_hostkey_fingerprint_sha256/1,
84         ssh_hostkey_fingerprint_sha384/1,
85         ssh_hostkey_fingerprint_sha512/1,
86         ssh_hostkey_fingerprint_list/1,
87
88         chk_known_hosts/1
89        ]).
90
91-include_lib("common_test/include/ct.hrl").
92-include_lib("public_key/include/public_key.hrl").
93-include("ssh_test_lib.hrl").
94
95%%%----------------------------------------------------------------
96%%% Common Test interface functions -------------------------------
97%%%----------------------------------------------------------------
98
99suite() ->
100    [{ct_hooks,[ts_install_cth]},
101     {timetrap,{seconds,40}}].
102
103all() ->
104    [{group, old_format},
105     {group, new_format},
106     {group, option_space},
107     {group, ssh_hostkey_fingerprint},
108     {group, ssh_public_key_decode_encode},
109     chk_known_hosts
110    ].
111
112
113-define(tests_old, [connect_rsa_sha2_to_rsa_sha2,
114                    connect_rsa_sha1_to_dsa,
115                    connect_rsa_sha2_to_dsa,
116                    connect_rsa_sha2_to_ecdsa,
117                    connect_dsa_to_rsa_sha2,
118                    connect_dsa_to_dsa,
119                    connect_dsa_to_ecdsa,
120                    connect_ecdsa_to_rsa_sha2,
121                    connect_ecdsa_to_dsa,
122                    connect_ecdsa_to_ecdsa
123                   ]).
124
125-define(tests_new, [connect_dsa_to_ed25519,
126                    connect_dsa_to_ed448,
127                    connect_ecdsa_to_ed25519,
128                    connect_ecdsa_to_ed448,
129                    connect_ed25519_to_dsa,
130                    connect_ed25519_to_ecdsa,
131                    connect_ed25519_to_ed448,
132                    connect_ed25519_to_ed25519,
133                    connect_ed25519_to_rsa_sha2,
134                    connect_ed448_to_dsa,
135                    connect_ed448_to_ecdsa,
136                    connect_ed448_to_ed25519,
137                    connect_ed448_to_ed448,
138                    connect_ed448_to_rsa_sha2,
139                    connect_rsa_sha2_to_ed25519,
140                    connect_rsa_sha2_to_ed448
141                    | ?tests_old % but taken from the new format directory
142                   ]).
143
144groups() ->
145    [{new_format,  [], ?tests_new},
146     {old_format,  [], [check_dsa_disabled, check_rsa_sha1_disabled | ?tests_old++[{group,passphrase}] ]},
147     {passphrase,  [], ?tests_old},
148     {option_space,[], [{group,new_format}]},
149
150     {ssh_hostkey_fingerprint, [],
151      [ssh_hostkey_fingerprint_md5_implicit,
152       ssh_hostkey_fingerprint_md5,
153       ssh_hostkey_fingerprint_sha,
154       ssh_hostkey_fingerprint_sha256,
155       ssh_hostkey_fingerprint_sha384,
156       ssh_hostkey_fingerprint_sha512,
157       ssh_hostkey_fingerprint_list]},
158
159     {ssh_public_key_decode_encode, [],
160      [ssh_rsa_public_key, ssh_dsa_public_key, ssh_ecdsa_public_key,
161       ssh_rfc4716_rsa_comment, ssh_rfc4716_dsa_comment,
162       ssh_rfc4716_rsa_subject,
163       ssh_list_public_key,
164       ssh_known_hosts, %% ssh1_known_hosts,
165       ssh_auth_keys, %% ssh1_auth_keys,
166       ssh_openssh_key_with_comment,
167       ssh_openssh_key_long_header]}
168    ].
169
170
171%%%----------------------------------------------------------------
172init_per_suite(Config) ->
173    ?CHECK_CRYPTO(
174       begin
175	   ssh:start(),
176	   [{client_opts,[]},
177            {daemon_opts,[]}
178            | Config]
179       end).
180
181end_per_suite(_onfig) ->
182    ssh:stop().
183
184%%%----------------------------------------------------------------
185init_per_group(new_format, Config) ->
186    Dir = filename:join(proplists:get_value(data_dir,Config), "new_format"),
187    [{fmt,new_format},
188     {key_src_dir,Dir} | Config];
189
190init_per_group(old_format, Config) ->
191    Dir = filename:join(proplists:get_value(data_dir,Config), "old_format"),
192    [{fmt,old_format},
193     {key_src_dir,Dir} | Config];
194
195init_per_group(option_space, Config) ->
196    extend_optsL([client_opts,daemon_opts],
197                 [{key_cb, {ssh_file, [{optimize, space}]}}],
198                 Config);
199
200init_per_group(passphrase, Config0) ->
201    case supported(hashs, md5) of
202        true ->
203            Dir = filename:join(proplists:get_value(data_dir,Config0), "old_format_passphrase"),
204            PassPhrases = [{K,"somepwd"} || K <- [dsa_pass_phrase,
205                                                  rsa_pass_phrase,
206                                                  ecdsa_pass_phrase]],
207            Config1 = extend_optsL(client_opts, PassPhrases, Config0),
208            replace_opt(key_src_dir, Dir, Config1);
209        false ->
210            {skip, "Unsupported hash"}
211    end;
212
213init_per_group(ssh_public_key_decode_encode, Config) ->
214    [{pk_data_dir,
215      filename:join([proplists:get_value(data_dir, Config),
216                     "public_key"])
217     } | Config];
218
219init_per_group(_, Config) ->
220    Config.
221
222
223extend_optsL(OptNames, Values, Config) when is_list(OptNames) ->
224    lists:foldl(fun(N, Cnf) ->
225                        extend_optsL(N, Values, Cnf)
226                end, Config, OptNames);
227extend_optsL(OptName, Values, Config) when is_atom(OptName) ->
228    Opts = proplists:get_value(OptName, Config),
229    replace_opt(OptName, Values ++ Opts, Config).
230
231replace_opt(OptName, Value, Config) ->
232    lists:keyreplace(OptName, 1, Config, {OptName,Value}).
233
234
235
236end_per_group(_, Config) ->
237    Config.
238
239%%%----------------------------------------------------------------
240init_per_testcase(connect_rsa_sha2_to_rsa_sha2, Config0) ->
241    setup_user_system_dir(rsa_sha2, rsa_sha2, Config0);
242init_per_testcase(connect_rsa_sha1_to_dsa, Config0) ->
243    setup_user_system_dir(rsa_sha1, dsa, Config0);
244init_per_testcase(connect_rsa_sha2_to_dsa, Config0) ->
245    setup_user_system_dir(rsa_sha2, dsa, Config0);
246init_per_testcase(connect_rsa_sha2_to_ecdsa, Config0) ->
247    setup_user_system_dir(rsa_sha2, ecdsa, Config0);
248init_per_testcase(connect_rsa_sha2_to_ed25519, Config0) ->
249    setup_user_system_dir(rsa_sha2, ed25519, Config0);
250init_per_testcase(connect_rsa_sha2_to_ed448, Config0) ->
251    setup_user_system_dir(rsa_sha2, ed448, Config0);
252init_per_testcase(connect_dsa_to_rsa_sha2, Config0) ->
253    setup_user_system_dir(dsa, rsa_sha2, Config0);
254init_per_testcase(connect_dsa_to_dsa, Config0) ->
255    setup_user_system_dir(dsa, dsa, Config0);
256init_per_testcase(connect_dsa_to_ecdsa, Config0) ->
257    setup_user_system_dir(dsa, ecdsa, Config0);
258init_per_testcase(connect_dsa_to_ed25519, Config0) ->
259    setup_user_system_dir(dsa, ed25519, Config0);
260init_per_testcase(connect_dsa_to_ed448, Config0) ->
261    setup_user_system_dir(dsa, ed448, Config0);
262init_per_testcase(connect_ecdsa_to_rsa_sha2, Config0) ->
263    setup_user_system_dir(ecdsa, rsa_sha2, Config0);
264init_per_testcase(connect_ecdsa_to_dsa, Config0) ->
265    setup_user_system_dir(ecdsa, dsa, Config0);
266init_per_testcase(connect_ecdsa_to_ecdsa, Config0) ->
267    setup_user_system_dir(ecdsa, ecdsa, Config0);
268init_per_testcase(connect_ecdsa_to_ed25519, Config0) ->
269    setup_user_system_dir(ecdsa, ed25519, Config0);
270init_per_testcase(connect_ecdsa_to_ed448, Config0) ->
271    setup_user_system_dir(ecdsa, ed448, Config0);
272init_per_testcase(connect_ed25519_to_rsa_sha2, Config0) ->
273    setup_user_system_dir(ed25519, rsa_sha2, Config0);
274init_per_testcase(connect_ed25519_to_dsa, Config0) ->
275    setup_user_system_dir(ed25519, dsa, Config0);
276init_per_testcase(connect_ed25519_to_ecdsa, Config0) ->
277    setup_user_system_dir(ed25519, ecdsa, Config0);
278init_per_testcase(connect_ed25519_to_ed25519, Config0) ->
279    setup_user_system_dir(ed25519, ed25519, Config0);
280init_per_testcase(connect_ed25519_to_ed448, Config0) ->
281    setup_user_system_dir(ed25519, ed448, Config0);
282init_per_testcase(connect_ed448_to_rsa_sha2, Config0) ->
283    setup_user_system_dir(ed448, rsa_sha2, Config0);
284init_per_testcase(connect_ed448_to_dsa, Config0) ->
285    setup_user_system_dir(ed448, dsa, Config0);
286init_per_testcase(connect_ed448_to_ecdsa, Config0) ->
287    setup_user_system_dir(ed448, ecdsa, Config0);
288init_per_testcase(connect_ed448_to_ed25519, Config0) ->
289    setup_user_system_dir(ed448, ed25519, Config0);
290init_per_testcase(connect_ed448_to_ed448, Config0) ->
291    setup_user_system_dir(ed448, ed448, Config0);
292
293init_per_testcase(check_dsa_disabled, Config0) ->
294    setup_default_user_system_dir(dsa, Config0);
295init_per_testcase(check_rsa_sha1_disabled, Config0) ->
296    setup_default_user_system_dir(rsa_sha1, Config0);
297
298init_per_testcase(ssh_hostkey_fingerprint_md5_implicit, Config) ->
299    init_fingerprint_testcase([md5], Config);
300
301init_per_testcase(ssh_hostkey_fingerprint_md5, Config) ->
302    init_fingerprint_testcase([md5], Config);
303
304init_per_testcase(ssh_hostkey_fingerprint_sha, Config) ->
305    init_fingerprint_testcase([sha], Config);
306
307init_per_testcase(ssh_hostkey_fingerprint_sha256, Config) ->
308    init_fingerprint_testcase([sha256], Config);
309
310init_per_testcase(ssh_hostkey_fingerprint_sha384, Config) ->
311    init_fingerprint_testcase([sha384], Config);
312
313init_per_testcase(ssh_hostkey_fingerprint_sha512, Config) ->
314    init_fingerprint_testcase([sha512], Config);
315
316init_per_testcase(ssh_hostkey_fingerprint_list  , Config) ->
317    init_fingerprint_testcase([sha,md5], Config);
318
319init_per_testcase(_, Config) ->
320    Config.
321
322
323end_per_testcase(_, Config) ->
324    Config.
325
326%%%----
327init_fingerprint_testcase(Algs, Config0) ->
328    Hashs = proplists:get_value(hashs, crypto:supports(), []),
329    case Algs -- Hashs of
330        [] ->
331            Config = lists:keydelete(watchdog, 1, Config0),
332            Dog = ct:timetrap(?TIMEOUT),
333            [{watchdog, Dog} | Config];
334        UnsupportedAlgs ->
335            {skip,{UnsupportedAlgs,not_supported}}
336    end.
337
338%%%----------------------------------------------------------------
339%%% Test Cases ----------------------------------------------------
340%%%----------------------------------------------------------------
341connect_rsa_sha2_to_rsa_sha2(Config) ->
342    try_connect(Config).
343
344connect_rsa_sha1_to_dsa(Config) ->
345    try_connect(Config).
346
347connect_rsa_sha2_to_dsa(Config) ->
348    try_connect(Config).
349
350connect_rsa_sha2_to_ecdsa(Config) ->
351    try_connect(Config).
352
353connect_rsa_sha2_to_ed25519(Config) ->
354    try_connect(Config).
355
356connect_rsa_sha2_to_ed448(Config) ->
357    try_connect(Config).
358
359connect_dsa_to_rsa_sha2(Config) ->
360    try_connect(Config).
361
362connect_dsa_to_dsa(Config) ->
363    try_connect(Config).
364
365connect_dsa_to_ecdsa(Config) ->
366    try_connect(Config).
367
368connect_dsa_to_ed25519(Config) ->
369    try_connect(Config).
370
371connect_dsa_to_ed448(Config) ->
372    try_connect(Config).
373
374connect_ecdsa_to_rsa_sha2(Config) ->
375    try_connect(Config).
376
377connect_ecdsa_to_dsa(Config) ->
378    try_connect(Config).
379
380connect_ecdsa_to_ecdsa(Config) ->
381    try_connect(Config).
382
383connect_ecdsa_to_ed25519(Config) ->
384    try_connect(Config).
385
386connect_ecdsa_to_ed448(Config) ->
387    try_connect(Config).
388
389connect_ed25519_to_rsa_sha2(Config) ->
390    try_connect(Config).
391
392connect_ed25519_to_dsa(Config) ->
393    try_connect(Config).
394
395connect_ed25519_to_ecdsa(Config) ->
396    try_connect(Config).
397
398connect_ed25519_to_ed25519(Config) ->
399    try_connect(Config).
400
401connect_ed25519_to_ed448(Config) ->
402    try_connect(Config).
403
404connect_ed448_to_rsa_sha2(Config) ->
405    try_connect(Config).
406
407connect_ed448_to_dsa(Config) ->
408    try_connect(Config).
409
410connect_ed448_to_ecdsa(Config) ->
411    try_connect(Config).
412
413connect_ed448_to_ed25519(Config) ->
414    try_connect(Config).
415
416connect_ed448_to_ed448(Config) ->
417    try_connect(Config).
418
419%%%----------------------------------------------------------------
420check_dsa_disabled(Config) ->
421    try_connect_disabled(Config).
422
423check_rsa_sha1_disabled(Config) ->
424    try_connect_disabled(Config).
425
426
427%%%----------------------------------------------------------------
428
429%% Check of different host keys left to later
430ssh_hostkey_fingerprint_md5_implicit(_Config) ->
431    Expected = "4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a",
432    Expected = ssh:hostkey_fingerprint(ssh_hostkey(rsa)).
433
434%%--------------------------------------------------------------------
435%% Check of different host keys left to later
436ssh_hostkey_fingerprint_md5(_Config) ->
437    Expected = "MD5:4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a",
438    Expected = ssh:hostkey_fingerprint(md5, ssh_hostkey(rsa)).
439
440%%--------------------------------------------------------------------
441%% Since this kind of fingerprint is not available yet on standard
442%% distros, we do like this instead. The Expected is generated with:
443%%       $ openssh-7.3p1/ssh-keygen -E sha1 -lf <file>
444%%       2048 SHA1:Soammnaqg06jrm2jivMSnzQGlmk none@example.org (RSA)
445ssh_hostkey_fingerprint_sha(_Config) ->
446    Expected = "SHA1:Soammnaqg06jrm2jivMSnzQGlmk",
447    Expected = ssh:hostkey_fingerprint(sha, ssh_hostkey(rsa)).
448
449%%--------------------------------------------------------------------
450%% Since this kind of fingerprint is not available yet on standard
451%% distros, we do like this instead.
452ssh_hostkey_fingerprint_sha256(_Config) ->
453    Expected = "SHA256:T7F1BahkJWR7iJO8+rpzWOPbp7LZP4MlNrDExdNYOvY",
454    Expected = ssh:hostkey_fingerprint(sha256, ssh_hostkey(rsa)).
455
456%%--------------------------------------------------------------------
457%% Since this kind of fingerprint is not available yet on standard
458%% distros, we do like this instead.
459ssh_hostkey_fingerprint_sha384(_Config) ->
460    Expected = "SHA384:QhkLoGNI4KXdPvC//HxxSCP3uTQVADqxdajbgm+Gkx9zqz8N94HyP1JmH8C4/aEl",
461    Expected = ssh:hostkey_fingerprint(sha384, ssh_hostkey(rsa)).
462
463%%--------------------------------------------------------------------
464%% Since this kind of fingerprint is not available yet on standard
465%% distros, we do like this instead.
466ssh_hostkey_fingerprint_sha512(_Config) ->
467    Expected = "SHA512:ezUismvm3ADQQb6Nm0c1DwQ6ydInlJNfsnSQejFkXNmABg1Aenk9oi45CXeBOoTnlfTsGG8nFDm0smP10PBEeA",
468    Expected = ssh:hostkey_fingerprint(sha512, ssh_hostkey(rsa)).
469
470%%--------------------------------------------------------------------
471%% Since this kind of fingerprint is not available yet on standard
472%% distros, we do like this instead.
473ssh_hostkey_fingerprint_list(_Config) ->
474    Expected = ["SHA1:Soammnaqg06jrm2jivMSnzQGlmk",
475                "MD5:4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a"],
476    Expected = ssh:hostkey_fingerprint([sha,md5], ssh_hostkey(rsa)).
477
478%%--------------------------------------------------------------------
479ssh_rsa_public_key(Config) when is_list(Config) ->
480    Datadir = proplists:get_value(pk_data_dir, Config),
481    {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_rsa_pub")),
482    [{PubKey, Attributes1}] = ssh_file:decode(RSARawSsh2, public_key),
483    [{PubKey, Attributes1}] = ssh_file:decode(RSARawSsh2, rfc4716_key),
484
485    {ok, RSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_rsa_pub")),
486    [{PubKey, Attributes2}] = ssh_file:decode(RSARawOpenSsh, public_key),
487    [{PubKey, Attributes2}] = ssh_file:decode(RSARawOpenSsh, openssh_key),
488
489    %% Can not check EncodedSSh == RSARawSsh2 and EncodedOpenSsh
490    %% = RSARawOpenSsh as line breakpoints may differ
491
492    EncodedSSh = ssh_file:encode([{PubKey, Attributes1}], rfc4716_key),
493    EncodedOpenSsh = ssh_file:encode([{PubKey, Attributes2}], openssh_key),
494
495    [{PubKey, Attributes1}] =
496	ssh_file:decode(EncodedSSh, public_key),
497    [{PubKey, Attributes2}] =
498	ssh_file:decode(EncodedOpenSsh, public_key).
499
500%%--------------------------------------------------------------------
501ssh_dsa_public_key(Config) when is_list(Config) ->
502    Datadir = proplists:get_value(pk_data_dir, Config),
503
504    {ok, DSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_dsa_pub")),
505    [{PubKey, Attributes1}] = ssh_file:decode(DSARawSsh2, public_key),
506    [{PubKey, Attributes1}] = ssh_file:decode(DSARawSsh2, rfc4716_key),
507
508    {ok, DSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_dsa_pub")),
509    [{PubKey, Attributes2}] = ssh_file:decode(DSARawOpenSsh, public_key),
510    [{PubKey, Attributes2}] = ssh_file:decode(DSARawOpenSsh, openssh_key),
511
512    %% Can not check EncodedSSh == DSARawSsh2 and EncodedOpenSsh
513    %% = DSARawOpenSsh as line breakpoints may differ
514
515    EncodedSSh = ssh_file:encode([{PubKey, Attributes1}], rfc4716_key),
516    EncodedOpenSsh = ssh_file:encode([{PubKey, Attributes2}], openssh_key),
517
518    [{PubKey, Attributes1}] =
519	ssh_file:decode(EncodedSSh, public_key),
520    [{PubKey, Attributes2}] =
521	ssh_file:decode(EncodedOpenSsh, public_key).
522
523%%--------------------------------------------------------------------
524ssh_ecdsa_public_key(Config) when is_list(Config) ->
525    Datadir = proplists:get_value(pk_data_dir, Config),
526
527    {ok, ECDSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_ecdsa_pub")),
528    [{PubKey, Attributes1}] = ssh_file:decode(ECDSARawSsh2, public_key),
529    [{PubKey, Attributes1}] = ssh_file:decode(ECDSARawSsh2, rfc4716_key),
530
531    {ok, ECDSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_ecdsa_pub")),
532    [{PubKey, Attributes2}] = ssh_file:decode(ECDSARawOpenSsh, public_key),
533    [{PubKey, Attributes2}] =ssh_file:decode(ECDSARawOpenSsh, openssh_key),
534
535    %% Can not check EncodedSSh == ECDSARawSsh2 and EncodedOpenSsh
536    %% = ECDSARawOpenSsh as line breakpoints may differ
537
538    EncodedSSh = ssh_file:encode([{PubKey, Attributes1}], rfc4716_key),
539    EncodedOpenSsh = ssh_file:encode([{PubKey, Attributes2}], openssh_key),
540
541    [{PubKey, Attributes1}] =
542	ssh_file:decode(EncodedSSh, public_key),
543    [{PubKey, Attributes2}] =
544	ssh_file:decode(EncodedOpenSsh, public_key).
545
546%%--------------------------------------------------------------------
547ssh_list_public_key(Config) when is_list(Config) ->
548    DataDir = proplists:get_value(pk_data_dir, Config),
549    {Data_ssh2, Expect_ssh2} =
550        collect_binaries_expected(DataDir, rfc4716_key,
551                                  ["ssh2_rsa_pub", "ssh2_rsa_comment_pub",
552                                   "ssh2_dsa_pub", "ssh2_dsa_comment_pub",
553                                   "ssh2_ecdsa_pub",
554                                   "ssh2_subject_pub"]),
555    {Data_openssh, Expect_openssh} =
556        collect_binaries_expected(DataDir, openssh_key,
557                                  ["openssh_rsa_pub", "openssh_dsa_pub", "openssh_ecdsa_pub"]),
558
559    true =
560        (chk_decode(Data_openssh,   Expect_openssh, openssh_key) and
561         chk_decode(Data_ssh2,      Expect_ssh2,    rfc4716_key) and
562         chk_decode(Data_openssh,   Expect_openssh, public_key)         and
563         chk_decode(Data_ssh2,      Expect_ssh2,    public_key)         and
564         chk_encode(Expect_openssh, openssh_key) and
565         chk_encode(Expect_ssh2,    rfc4716_key)
566        ).
567
568chk_encode(Data, Type) ->
569    case ssh_file:decode(ssh_file:encode(Data,Type), Type) of
570        Data->
571            ct:log("re-encode ~p ok", [Type]),
572            true;
573        Result ->
574            ct:log("re-encode ~p FAILED~n"
575                   "Got~n ~p~nExpect~n ~p~n",
576                   [Type, Result, Data]),
577            false
578    end.
579
580
581chk_decode(Data, Expect, Type) ->
582    case ssh_file:decode(Data, Type) of
583        Expect ->
584            ct:log("decode ~p ok", [Type]),
585            true;
586        BadResult ->
587            ct:log("decode ~p FAILED~n"
588                   "Result~n ~p~nExpect~n ~p~n"
589                   "~p",
590                   [Type, BadResult, Expect,
591                    if
592                        is_list(BadResult) ->
593                            lists:foldr(fun({Key,Attrs}, Acc) ->
594                                                case Key of
595                                                    #'RSAPublicKey'{} when is_list(Attrs) -> Acc;
596                                                    {_, #'Dss-Parms'{}} when is_list(Attrs) -> Acc;
597                                                    {#'ECPoint'{}, {namedCurve,_}} when is_list(Attrs) -> Acc;
598                                                    _  when is_list(Attrs) -> [{bad_key,{Key,Attrs}}|Acc];
599                                                    _ -> [{bad_attrs,{Key,Attrs}}|Acc]
600                                                end;
601                                           (Other,Acc) ->
602                                                [{other,Other}|Acc]
603                                        end, [], BadResult);
604                        true ->
605                            '???'
606                    end]),
607            false
608    end.
609
610
611collect_binaries_expected(Dir, Type, Files) ->
612    Bins0 = [B || F <- Files,
613                  {ok,B} <- [ file:read_file(filename:join(Dir,F)) ]
614            ],
615    {list_to_binary( lists:join("\n", Bins0)),
616     lists:flatten([ssh_file:decode(B,Type) || B <- Bins0])}.
617
618%%--------------------------------------------------------------------
619ssh_rfc4716_rsa_comment(Config) when is_list(Config) ->
620    Datadir = proplists:get_value(pk_data_dir, Config),
621
622    {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_rsa_comment_pub")),
623    [{#'RSAPublicKey'{} = PubKey, Attributes}] =
624        ssh_file:decode(RSARawSsh2, public_key),
625
626    Headers = proplists:get_value(headers, Attributes),
627
628    Value = proplists:get_value("Comment", Headers, undefined),
629    true = Value =/= undefined,
630    RSARawSsh2 = ssh_file:encode([{PubKey, Attributes}], rfc4716_key).
631
632%%--------------------------------------------------------------------
633ssh_rfc4716_dsa_comment(Config) when is_list(Config) ->
634    Datadir = proplists:get_value(pk_data_dir, Config),
635
636    {ok, DSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_dsa_comment_pub")),
637    [{{_, #'Dss-Parms'{}} = PubKey, Attributes}] =
638        ssh_file:decode(DSARawSsh2, public_key),
639
640    Headers = proplists:get_value(headers, Attributes),
641
642    Value = proplists:get_value("Comment", Headers, undefined),
643    true = Value =/= undefined,
644
645    %% Can not check Encoded == DSARawSsh2 as line continuation breakpoints may differ
646    Encoded  = ssh_file:encode([{PubKey, Attributes}], rfc4716_key),
647    [{PubKey, Attributes}] =
648        ssh_file:decode(Encoded, public_key).
649
650%%--------------------------------------------------------------------
651ssh_rfc4716_rsa_subject(Config) when is_list(Config) ->
652    Datadir = proplists:get_value(pk_data_dir, Config),
653
654    {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_subject_pub")),
655    [{#'RSAPublicKey'{} = PubKey, Attributes}] =
656        ssh_file:decode(RSARawSsh2, public_key),
657
658    Headers = proplists:get_value(headers, Attributes),
659
660    Value = proplists:get_value("Subject", Headers, undefined),
661    true = Value =/= undefined,
662
663    %% Can not check Encoded == RSARawSsh2 as line continuation breakpoints may differ
664    Encoded  = ssh_file:encode([{PubKey, Attributes}], rfc4716_key),
665    [{PubKey, Attributes}] =
666        ssh_file:decode(Encoded, public_key).
667
668%%--------------------------------------------------------------------
669ssh_known_hosts(Config) when is_list(Config) ->
670    Datadir = proplists:get_value(pk_data_dir, Config),
671
672    {ok, SshKnownHosts} = file:read_file(filename:join(Datadir, "known_hosts")),
673    [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2},
674     {#'RSAPublicKey'{}, Attributes3}, {#'RSAPublicKey'{}, Attributes4}] = Decoded =
675        ssh_file:decode(SshKnownHosts, known_hosts),
676
677    Comment1 = undefined,
678    Comment2 = "foo@bar.com",
679    Comment3 = "Comment with whitespaces",
680    Comment4 = "foo@bar.com Comment with whitespaces",
681
682    Comment1 = proplists:get_value(comment, Attributes1, undefined),
683    Comment2 = proplists:get_value(comment, Attributes2),
684    Comment3 = proplists:get_value(comment, Attributes3),
685    Comment4 = proplists:get_value(comment, Attributes4),
686
687    Value1 = proplists:get_value(hostnames, Attributes1, undefined),
688    Value2 = proplists:get_value(hostnames, Attributes2, undefined),
689    true = (Value1 =/= undefined) and (Value2 =/= undefined),
690
691    Encoded = ssh_file:encode(Decoded, known_hosts),
692    Decoded = ssh_file:decode(Encoded, known_hosts).
693
694%%--------------------------------------------------------------------
695ssh1_known_hosts(Config) when is_list(Config) ->
696    Datadir = proplists:get_value(pk_data_dir, Config),
697
698    {ok, SshKnownHosts} = file:read_file(filename:join(Datadir, "ssh1_known_hosts")),
699    [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2},{#'RSAPublicKey'{}, Attributes3}]
700	= Decoded = ssh_file:decode(SshKnownHosts, known_hosts),
701
702    Value1 = proplists:get_value(hostnames, Attributes1, undefined),
703    Value2 = proplists:get_value(hostnames, Attributes2, undefined),
704    true = (Value1 =/= undefined) and (Value2 =/= undefined),
705
706    Comment ="dhopson@VMUbuntu-DSH comment with whitespaces",
707    Comment = proplists:get_value(comment, Attributes3),
708
709    Encoded = ssh_file:encode(Decoded, known_hosts),
710    Decoded = ssh_file:decode(Encoded, known_hosts).
711
712%%--------------------------------------------------------------------
713ssh_auth_keys(Config) when is_list(Config) ->
714    Datadir = proplists:get_value(pk_data_dir, Config),
715
716    {ok, SshAuthKeys} = file:read_file(filename:join(Datadir, "auth_keys")),
717    [{#'RSAPublicKey'{}, Attributes1}, {{_, #'Dss-Parms'{}}, Attributes2},
718     {#'RSAPublicKey'{}, Attributes3}, {{_, #'Dss-Parms'{}}, Attributes4}
719    ] = Decoded =
720        ssh_file:decode(SshAuthKeys, auth_keys),
721
722    Value1 = proplists:get_value(options, Attributes1, undefined),
723    true = Value1 =/= undefined,
724
725    Comment1 = Comment2 = "dhopson@VMUbuntu-DSH",
726    Comment3 = Comment4 ="dhopson@VMUbuntu-DSH comment with whitespaces",
727
728    Comment1 = proplists:get_value(comment, Attributes1),
729    Comment2 = proplists:get_value(comment, Attributes2),
730    Comment3 = proplists:get_value(comment, Attributes3),
731    Comment4 = proplists:get_value(comment, Attributes4),
732
733    Encoded = ssh_file:encode(Decoded, auth_keys),
734    Decoded = ssh_file:decode(Encoded, auth_keys).
735
736%%--------------------------------------------------------------------
737ssh1_auth_keys(Config) when is_list(Config) ->
738    Datadir = proplists:get_value(pk_data_dir, Config),
739
740    {ok, SshAuthKeys} = file:read_file(filename:join(Datadir, "ssh1_auth_keys")),
741    [{#'RSAPublicKey'{}, Attributes1},
742     {#'RSAPublicKey'{}, Attributes2}, {#'RSAPublicKey'{}, Attributes3},
743     {#'RSAPublicKey'{}, Attributes4}, {#'RSAPublicKey'{}, Attributes5}] = Decoded =
744        ssh_file:decode(SshAuthKeys, auth_keys),
745
746    Value1 = proplists:get_value(bits, Attributes2, undefined),
747    Value2 = proplists:get_value(bits, Attributes3, undefined),
748    true = (Value1 =/= undefined) and (Value2 =/= undefined),
749
750    Comment2 = Comment3 = "dhopson@VMUbuntu-DSH",
751    Comment4 = Comment5 ="dhopson@VMUbuntu-DSH comment with whitespaces",
752
753    undefined = proplists:get_value(comment, Attributes1, undefined),
754    Comment2 = proplists:get_value(comment, Attributes2),
755    Comment3 = proplists:get_value(comment, Attributes3),
756    Comment4 = proplists:get_value(comment, Attributes4),
757    Comment5 = proplists:get_value(comment, Attributes5),
758
759    Encoded = ssh_file:encode(Decoded, auth_keys),
760    Decoded = ssh_file:decode(Encoded, auth_keys).
761
762%%--------------------------------------------------------------------
763ssh_openssh_key_with_comment(Config) when is_list(Config) ->
764    Datadir = proplists:get_value(pk_data_dir, Config),
765
766    {ok, DSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_dsa_with_comment_pub")),
767    [{{_, #'Dss-Parms'{}}, _}] = ssh_file:decode(DSARawOpenSsh, openssh_key).
768
769%%--------------------------------------------------------------------
770ssh_openssh_key_long_header(Config) when is_list(Config) ->
771    Datadir = proplists:get_value(pk_data_dir, Config),
772
773    {ok,RSARawOpenSsh} = file:read_file(filename:join(Datadir, "ssh_rsa_long_header_pub")),
774    [{#'RSAPublicKey'{}, _}] = Decoded = ssh_file:decode(RSARawOpenSsh, public_key),
775
776    Encoded = ssh_file:encode(Decoded, rfc4716_key),
777    Decoded = ssh_file:decode(Encoded, rfc4716_key).
778
779%%%----------------------------------------------------------------
780%%% Test case helpers
781%%%----------------------------------------------------------------
782%% Should use stored keys instead
783ssh_hostkey(rsa) ->
784    [{PKdecoded,_}] =
785	ssh_file:decode(
786	  <<"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYXcYmsyJBstl4EfFYzfQJmSiUE162zvSGSoMYybShYOI6rnnyvvihfw8Aml+2gZ716F2tqG48FQ/yPZEGWNPMrCejPpJctaPWhpNdNMJ8KFXSEgr5bY2mEpa19DHmuDeXKzeJJ+X7s3fVdYc4FMk5731KIW6Huf019ZnTxbx0VKG6b1KAJBg3vpNsDxEMwQ4LFMB0JHVklOTzbxmpaeULuIxvl65A+eGeFVeo2Q+YI9UnwY1vSgmc9Azwy8Ie9Z0HpQBN5I7Uc5xnknT8V6xDhgNfXEfzsgsRdDfZLECt1WO/1gP9wkosvAGZWt5oG8pbNQWiQdFq536ck8WQD9WD none@example.org">>,
787	  public_key),
788    PKdecoded.
789
790%%%----------------------------------------------------------------
791chk_known_hosts(Config) ->
792    PrivDir = proplists:get_value(priv_dir, Config),
793
794    DataDir = filename:join(proplists:get_value(data_dir,Config), "new_format"),
795    SysDir = filename:join(PrivDir, "chk_known_hosts_sys_dir"),
796    ssh_test_lib:setup_all_host_keys(DataDir, SysDir),
797
798    UsrDir = filename:join(PrivDir, "chk_known_hosts_usr_dir"),
799    file:make_dir(UsrDir),
800    KnownHostsFile = filename:join(UsrDir, "known_hosts"),
801
802    DaemonOpts = [{system_dir, SysDir},
803                  {user_dir, UsrDir},
804                  {password, "bar"}],
805
806    UserOpts = [{user_dir, UsrDir},
807                {user, "foo"},
808                {password, "bar"},
809                {silently_accept_hosts, true},
810                {user_interaction, false}
811               ],
812
813    {_Pid1, Host1, Port1} = ssh_test_lib:daemon(DaemonOpts),
814    {_Pid2, Host2, Port2} = ssh_test_lib:daemon(DaemonOpts),
815
816    _C1 = ssh_test_lib:connect(Host1, Port1, UserOpts),
817    {ok,KnownHosts1} = file:read_file(KnownHostsFile),
818    Sz1 = byte_size(KnownHosts1),
819    ct:log("~p bytes KnownHosts1 = ~p", [Sz1, KnownHosts1]),
820
821    _C2 = ssh_test_lib:connect(Host2, Port2, UserOpts),
822    {ok,KnownHosts2} = file:read_file(KnownHostsFile),
823    Sz2 = byte_size(KnownHosts2),
824    ct:log("~p bytes KnownHosts2 = ~p", [Sz2, KnownHosts2]),
825
826    %% Check that 2nd is appended after the 1st:
827    <<KnownHosts1:Sz1/binary, _/binary>> = KnownHosts2,
828
829    %% Check that there are exactly two NLs:
830    2 = lists:foldl(fun($\n, Sum) -> Sum + 1;
831                       (_,   Sum) -> Sum
832                    end, 0, binary_to_list(KnownHosts2)),
833
834    %% Check that at least one NL terminates both two lines:
835    <<_:(Sz1-1)/binary, $\n, _:(Sz2-Sz1-1)/binary, $\n>> = KnownHosts2.
836
837
838%%%----------------------------------------------------------------
839try_connect({skip,Reson}) ->
840    {skip,Reson};
841try_connect(Config) ->
842    SystemDir = proplists:get_value(system_dir, Config),
843    UserDir = proplists:get_value(user_dir, Config),
844    ClientOpts = proplists:get_value(client_opts, Config, []),
845    DaemonOpts = proplists:get_value(daemon_opts, Config, []),
846
847    ssh_dbg:start(fun ct:log/2), ssh_dbg:on([alg]),
848    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
849					     {user_dir, UserDir}
850                                             | DaemonOpts]),
851
852    C = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
853                                          {silently_accept_hosts, true},
854                                          {user_interaction, false}
855                                          | ClientOpts]),
856    ssh:close(C),
857    ssh_dbg:stop(),
858    ssh:stop_daemon(Pid).
859
860
861try_connect_disabled(Config) ->
862    try try_connect(Config)
863    of _ -> {fail, "non-default algorithm accepted"}
864    catch error:{badmatch,{error,"Service not available"}} -> ok
865    end.
866
867%%%----------------------------------------------------------------
868%%% Local ---------------------------------------------------------
869%%%----------------------------------------------------------------
870setup_user_system_dir(ClientAlg, ServerAlg, Config) ->
871    case supported(public_key, ClientAlg) andalso supported(public_key, ServerAlg) of
872        true ->
873            try
874                setup_dirs(ClientAlg, ServerAlg, Config)
875            of
876                {ok, {SystemDir,UserDir}} ->
877                    ModAlgs = [{preferred_algorithms,
878                                [{public_key, lists:usort([alg(ClientAlg), alg(ServerAlg)])}]
879                               }],
880                    [{system_dir,SystemDir},
881                     {user_dir,UserDir}
882                     | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
883            catch
884                error:{badmatch,{error,enoent}}:S ->
885                    ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]),
886                    {skip, no_key_file_found}
887            end;
888
889        false ->
890            {skip, unsupported_algorithm}
891    end.
892
893
894setup_default_user_system_dir(ClientAlg, Config) ->
895    ServerAlg = ecdsa,
896    case default(public_key, ClientAlg) of
897        false ->
898            case supported(public_key, ClientAlg) of
899                true ->
900                    case supported(public_key, ServerAlg) of
901                        true ->
902                            try
903                                setup_dirs(ClientAlg, ServerAlg, Config)
904                            of
905                                {ok, {SystemDir,UserDir}} ->
906                                    ModAlgs = [{modify_algorithms,
907                                                [{append,[{public_key,[alg(ServerAlg)]}]},
908                                                 {rm, [{public_key,[alg(ClientAlg)|inv_algs(ClientAlg)]}]}
909                                                ]}],
910                                    [{system_dir,SystemDir},
911                                     {user_dir,UserDir}
912                                     | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
913                            catch
914                                error:{badmatch,{error,enoent}}:S ->
915                                    ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]),
916                                    {skip, no_key_file_found}
917                            end;
918                        false ->
919                            {skip, unsupported_server_algorithm}
920                    end;
921                false ->
922                    {skip, unsupported_client_algorithm}
923            end;
924        true ->
925            {fail, disabled_algorithm_present}
926    end.
927
928
929setup_dirs(ClientAlg, ServerAlg, Config) ->
930    PrivDir = proplists:get_value(priv_dir, Config),
931    KeySrcDir = proplists:get_value(key_src_dir, Config),
932    Fmt = proplists:get_value(fmt, Config),
933
934    System = lists:concat(["system_", ClientAlg, "_", ServerAlg, "_", Fmt]),
935    SystemDir = filename:join(PrivDir, System),
936    file:make_dir(SystemDir),
937
938    User   = lists:concat(["user_", ClientAlg, "_", ServerAlg, "_", Fmt]),
939    UserDir   = filename:join(PrivDir, User),
940    file:make_dir(UserDir),
941
942    HostSrcFile = filename:join(KeySrcDir, file(src,host,ServerAlg)),
943    HostDstFile = filename:join(SystemDir, file(dst,host,ServerAlg)),
944
945    UserSrcFile = filename:join(KeySrcDir, file(src,user,ClientAlg)),
946    UserDstFile = filename:join(UserDir, file(dst,user,ClientAlg)),
947
948    UserPubSrcFile = filename:join(KeySrcDir, file(src,user,ClientAlg)++".pub"),
949    AuthorizedKeys = filename:join(UserDir, "authorized_keys"),
950
951    ct:log("UserSrcFile = ~p~nUserDstFile = ~p", [UserSrcFile, UserDstFile]),
952    {ok,_} = file:copy(UserSrcFile, UserDstFile),
953    ct:log("UserPubSrcFile = ~p~nAuthorizedKeys = ~p", [UserPubSrcFile, AuthorizedKeys]),
954    {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
955    ct:log("HostSrcFile = ~p~nHostDstFile = ~p", [HostSrcFile, HostDstFile]),
956    {ok,_} = file:copy(HostSrcFile, HostDstFile),
957
958    ct:log("SystemDir = ~p~nUserDir = ~p", [SystemDir,UserDir]),
959    {ok, {SystemDir,UserDir}}.
960
961%%%----------------------------------------------------------------
962file(  _, host, dsa)     -> "ssh_host_dsa_key";
963file(  _, host, ecdsa)   -> "ssh_host_ecdsa_key";
964file(  _, host, ed25519) -> "ssh_host_ed25519_key";
965file(  _, host, ed448)   -> "ssh_host_ed448_key";
966file(  _, host, rsa_sha2)-> "ssh_host_rsa_key";
967file(src, host, rsa_sha1)-> "ssh_host_rsa_key";
968file(dst, host, rsa_sha1)-> "ssh_host_rsa_key";
969file(  _, user, dsa)     -> "id_dsa";
970file(  _, user, ecdsa)   -> "id_ecdsa";
971file(  _, user, ed25519) -> "id_ed25519";
972file(  _, user, ed448)   -> "id_ed448";
973file(  _, user, rsa_sha2)-> "id_rsa";
974file(src, user, rsa_sha1)-> "id_rsa";
975file(dst, user, rsa_sha1)-> "id_rsa".
976
977alg(dsa)     -> 'ssh-dss';
978alg(ecdsa)   -> 'ecdsa-sha2-nistp256';
979alg(ed25519) -> 'ssh-ed25519';
980alg(ed448)   -> 'ssh-ed448';
981alg(rsa_sha2)-> 'rsa-sha2-256';
982alg(rsa_sha1)-> 'ssh-rsa'.
983
984inv_algs(rsa_sha1) -> algs(rsa_sha2);
985inv_algs(_) -> [].
986
987algs(dsa)     -> ['ssh-dss'];
988algs(ecdsa)   -> ['ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-521'];
989algs(ed25519) -> ['ssh-ed25519'];
990algs(ed448)   -> ['ssh-ed448'];
991algs(rsa_sha2)-> ['rsa-sha2-256', 'rsa-sha2-384', 'rsa-sha2-512'];
992algs(rsa_sha1)-> ['ssh-rsa'];
993algs(A) -> [A].
994
995
996
997default(Type, Alg) -> listed(algs(Alg), ssh_transport:default_algorithms(Type)).
998
999supported(Type, Alg) -> listed(algs(Alg),
1000                               try
1001                                   ssh_transport:supported_algorithms(Type)
1002                               catch
1003                                   error:function_clause -> crypto:supports(Type)
1004                               end).
1005
1006listed(As, L) -> lists:any(fun(A) -> lists:member(A,L) end,
1007                           As).
1008
1009
1010