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         connect_dsa_to_dsa/1,
38         connect_dsa_to_ecdsa/1,
39         connect_dsa_to_ed25519/1,
40         connect_dsa_to_ed448/1,
41         connect_dsa_to_rsa/1,
42         connect_ecdsa_to_dsa/1,
43         connect_ecdsa_to_ecdsa/1,
44         connect_ecdsa_to_ed25519/1,
45         connect_ecdsa_to_ed448/1,
46         connect_ecdsa_to_rsa/1,
47         connect_ed25519_to_dsa/1,
48         connect_ed25519_to_ecdsa/1,
49         connect_ed25519_to_ed25519/1,
50         connect_ed25519_to_ed448/1,
51         connect_ed25519_to_rsa/1,
52         connect_ed448_to_dsa/1,
53         connect_ed448_to_ecdsa/1,
54         connect_ed448_to_ed25519/1,
55         connect_ed448_to_ed448/1,
56         connect_ed448_to_rsa/1,
57         connect_rsa_to_dsa/1,
58         connect_rsa_to_ecdsa/1,
59         connect_rsa_to_ed25519/1,
60         connect_rsa_to_ed448/1,
61         connect_rsa_to_rsa/1,
62
63         chk_known_hosts/1
64        ]).
65
66-include_lib("common_test/include/ct.hrl").
67-include("ssh_test_lib.hrl").
68
69%%%----------------------------------------------------------------
70%%% Common Test interface functions -------------------------------
71%%%----------------------------------------------------------------
72
73suite() ->
74    [{ct_hooks,[ts_install_cth]},
75     {timetrap,{seconds,40}}].
76
77all() ->
78    [{group, old_format},
79     {group, new_format},
80     {group, option_space},
81     chk_known_hosts
82    ].
83
84
85-define(tests_old, [connect_rsa_to_rsa,
86                    connect_rsa_to_dsa,
87                    connect_rsa_to_ecdsa,
88                    connect_dsa_to_rsa,
89                    connect_dsa_to_dsa,
90                    connect_dsa_to_ecdsa,
91                    connect_ecdsa_to_rsa,
92                    connect_ecdsa_to_dsa,
93                    connect_ecdsa_to_ecdsa
94                   ]).
95
96-define(tests_new, [
97                    connect_dsa_to_ed25519,
98                    connect_dsa_to_ed448,
99                    connect_ecdsa_to_ed25519,
100                    connect_ecdsa_to_ed448,
101                    connect_ed25519_to_dsa,
102                    connect_ed25519_to_ecdsa,
103                    connect_ed25519_to_ed448,
104                    connect_ed25519_to_ed25519,
105                    connect_ed25519_to_rsa,
106                    connect_ed448_to_dsa,
107                    connect_ed448_to_ecdsa,
108                    connect_ed448_to_ed25519,
109                    connect_ed448_to_ed448,
110                    connect_ed448_to_rsa,
111                    connect_rsa_to_ed25519,
112                    connect_rsa_to_ed448
113                    | ?tests_old % but taken from the new format directory
114                   ]).
115
116groups() ->
117    [{new_format,  [], ?tests_new},
118     {old_format,  [], ?tests_old++[{group,passphrase}]},
119     {passphrase,  [], ?tests_old},
120     {option_space,[], [{group,new_format}]}
121    ].
122
123%%%----------------------------------------------------------------
124init_per_suite(Config) ->
125    ?CHECK_CRYPTO(
126       begin
127	   ssh:start(),
128	   [{client_opts,[]},
129            {daemon_opts,[]}
130            | Config]
131       end).
132
133end_per_suite(_onfig) ->
134    ssh:stop().
135
136%%%----------------------------------------------------------------
137init_per_group(new_format, Config) ->
138    Dir = filename:join(proplists:get_value(data_dir,Config), "new_format"),
139    [{fmt,new_format},
140     {key_src_dir,Dir} | Config];
141
142init_per_group(old_format, Config) ->
143    Dir = filename:join(proplists:get_value(data_dir,Config), "old_format"),
144    [{fmt,old_format},
145     {key_src_dir,Dir} | Config];
146
147init_per_group(option_space, Config) ->
148    extend_optsL([client_opts,daemon_opts],
149                 [{key_cb, {ssh_file, [{optimize, space}]}}],
150                 Config);
151
152init_per_group(passphrase, Config0) ->
153    case supported(hashs, md5) of
154        true ->
155            Dir = filename:join(proplists:get_value(data_dir,Config0), "old_format_passphrase"),
156            PassPhrases = [{K,"somepwd"} || K <- [dsa_pass_phrase,
157                                                  rsa_pass_phrase,
158                                                  ecdsa_pass_phrase]],
159            Config1 = extend_optsL(client_opts, PassPhrases, Config0),
160            replace_opt(key_src_dir, Dir, Config1);
161        false ->
162            {skip, "Unsupported hash"}
163    end;
164
165init_per_group(_, Config) ->
166    Config.
167
168
169extend_optsL(OptNames, Values, Config) when is_list(OptNames) ->
170    lists:foldl(fun(N, Cnf) ->
171                        extend_optsL(N, Values, Cnf)
172                end, Config, OptNames);
173extend_optsL(OptName, Values, Config) when is_atom(OptName) ->
174    Opts = proplists:get_value(OptName, Config),
175    replace_opt(OptName, Values ++ Opts, Config).
176
177replace_opt(OptName, Value, Config) ->
178    lists:keyreplace(OptName, 1, Config, {OptName,Value}).
179
180
181
182end_per_group(_, Config) ->
183    Config.
184
185%%%----------------------------------------------------------------
186init_per_testcase(connect_rsa_to_rsa, Config0) ->
187    setup_user_system_dir(rsa, rsa, Config0);
188init_per_testcase(connect_rsa_to_dsa, Config0) ->
189    setup_user_system_dir(rsa, dsa, Config0);
190init_per_testcase(connect_rsa_to_ecdsa, Config0) ->
191    setup_user_system_dir(rsa, ecdsa, Config0);
192init_per_testcase(connect_rsa_to_ed25519, Config0) ->
193    setup_user_system_dir(rsa, ed25519, Config0);
194init_per_testcase(connect_rsa_to_ed448, Config0) ->
195    setup_user_system_dir(rsa, ed448, Config0);
196init_per_testcase(connect_dsa_to_rsa, Config0) ->
197    setup_user_system_dir(dsa, rsa, Config0);
198init_per_testcase(connect_dsa_to_dsa, Config0) ->
199    setup_user_system_dir(dsa, dsa, Config0);
200init_per_testcase(connect_dsa_to_ecdsa, Config0) ->
201    setup_user_system_dir(dsa, ecdsa, Config0);
202init_per_testcase(connect_dsa_to_ed25519, Config0) ->
203    setup_user_system_dir(dsa, ed25519, Config0);
204init_per_testcase(connect_dsa_to_ed448, Config0) ->
205    setup_user_system_dir(dsa, ed448, Config0);
206init_per_testcase(connect_ecdsa_to_rsa, Config0) ->
207    setup_user_system_dir(ecdsa, rsa, Config0);
208init_per_testcase(connect_ecdsa_to_dsa, Config0) ->
209    setup_user_system_dir(ecdsa, dsa, Config0);
210init_per_testcase(connect_ecdsa_to_ecdsa, Config0) ->
211    setup_user_system_dir(ecdsa, ecdsa, Config0);
212init_per_testcase(connect_ecdsa_to_ed25519, Config0) ->
213    setup_user_system_dir(ecdsa, ed25519, Config0);
214init_per_testcase(connect_ecdsa_to_ed448, Config0) ->
215    setup_user_system_dir(ecdsa, ed448, Config0);
216init_per_testcase(connect_ed25519_to_rsa, Config0) ->
217    setup_user_system_dir(ed25519, rsa, Config0);
218init_per_testcase(connect_ed25519_to_dsa, Config0) ->
219    setup_user_system_dir(ed25519, dsa, Config0);
220init_per_testcase(connect_ed25519_to_ecdsa, Config0) ->
221    setup_user_system_dir(ed25519, ecdsa, Config0);
222init_per_testcase(connect_ed25519_to_ed25519, Config0) ->
223    setup_user_system_dir(ed25519, ed25519, Config0);
224init_per_testcase(connect_ed25519_to_ed448, Config0) ->
225    setup_user_system_dir(ed25519, ed448, Config0);
226init_per_testcase(connect_ed448_to_rsa, Config0) ->
227    setup_user_system_dir(ed448, rsa, Config0);
228init_per_testcase(connect_ed448_to_dsa, Config0) ->
229    setup_user_system_dir(ed448, dsa, Config0);
230init_per_testcase(connect_ed448_to_ecdsa, Config0) ->
231    setup_user_system_dir(ed448, ecdsa, Config0);
232init_per_testcase(connect_ed448_to_ed25519, Config0) ->
233    setup_user_system_dir(ed448, ed25519, Config0);
234init_per_testcase(connect_ed448_to_ed448, Config0) ->
235    setup_user_system_dir(ed448, ed448, Config0);
236init_per_testcase(_, Config) ->
237    Config.
238
239end_per_testcase(_, Config) ->
240    Config.
241
242%%%----------------------------------------------------------------
243%%% Test Cases ----------------------------------------------------
244%%%----------------------------------------------------------------
245connect_rsa_to_rsa(Config) ->
246    try_connect(Config).
247
248connect_rsa_to_dsa(Config) ->
249    try_connect(Config).
250
251connect_rsa_to_ecdsa(Config) ->
252    try_connect(Config).
253
254connect_rsa_to_ed25519(Config) ->
255    try_connect(Config).
256
257connect_rsa_to_ed448(Config) ->
258    try_connect(Config).
259
260connect_dsa_to_rsa(Config) ->
261    try_connect(Config).
262
263connect_dsa_to_dsa(Config) ->
264    try_connect(Config).
265
266connect_dsa_to_ecdsa(Config) ->
267    try_connect(Config).
268
269connect_dsa_to_ed25519(Config) ->
270    try_connect(Config).
271
272connect_dsa_to_ed448(Config) ->
273    try_connect(Config).
274
275connect_ecdsa_to_rsa(Config) ->
276    try_connect(Config).
277
278connect_ecdsa_to_dsa(Config) ->
279    try_connect(Config).
280
281connect_ecdsa_to_ecdsa(Config) ->
282    try_connect(Config).
283
284connect_ecdsa_to_ed25519(Config) ->
285    try_connect(Config).
286
287connect_ecdsa_to_ed448(Config) ->
288    try_connect(Config).
289
290connect_ed25519_to_rsa(Config) ->
291    try_connect(Config).
292
293connect_ed25519_to_dsa(Config) ->
294    try_connect(Config).
295
296connect_ed25519_to_ecdsa(Config) ->
297    try_connect(Config).
298
299connect_ed25519_to_ed25519(Config) ->
300    try_connect(Config).
301
302connect_ed25519_to_ed448(Config) ->
303    try_connect(Config).
304
305connect_ed448_to_rsa(Config) ->
306    try_connect(Config).
307
308connect_ed448_to_dsa(Config) ->
309    try_connect(Config).
310
311connect_ed448_to_ecdsa(Config) ->
312    try_connect(Config).
313
314connect_ed448_to_ed25519(Config) ->
315    try_connect(Config).
316
317connect_ed448_to_ed448(Config) ->
318    try_connect(Config).
319
320
321%%%----------------------------------------------------------------
322chk_known_hosts(Config) ->
323    PrivDir = proplists:get_value(priv_dir, Config),
324
325    DataDir = filename:join(proplists:get_value(data_dir,Config), "new_format"),
326    SysDir = filename:join(PrivDir, "chk_known_hosts_sys_dir"),
327    ssh_test_lib:setup_all_host_keys(DataDir, SysDir),
328
329    UsrDir = filename:join(PrivDir, "chk_known_hosts_usr_dir"),
330    file:make_dir(UsrDir),
331    KnownHostsFile = filename:join(UsrDir, "known_hosts"),
332
333    DaemonOpts = [{system_dir, SysDir},
334                  {user_dir, UsrDir},
335                  {password, "bar"}],
336
337    UserOpts = [{user_dir, UsrDir},
338                {user, "foo"},
339                {password, "bar"},
340                {silently_accept_hosts, true},
341                {user_interaction, false}
342               ],
343
344    {_Pid1, Host1, Port1} = ssh_test_lib:daemon(DaemonOpts),
345    {_Pid2, Host2, Port2} = ssh_test_lib:daemon(DaemonOpts),
346
347    _C1 = ssh_test_lib:connect(Host1, Port1, UserOpts),
348    {ok,KnownHosts1} = file:read_file(KnownHostsFile),
349    Sz1 = byte_size(KnownHosts1),
350    ct:log("~p bytes KnownHosts1 = ~p", [Sz1, KnownHosts1]),
351
352    _C2 = ssh_test_lib:connect(Host2, Port2, UserOpts),
353    {ok,KnownHosts2} = file:read_file(KnownHostsFile),
354    Sz2 = byte_size(KnownHosts2),
355    ct:log("~p bytes KnownHosts2 = ~p", [Sz2, KnownHosts2]),
356
357    %% Check that 2nd is appended after the 1st:
358    <<KnownHosts1:Sz1/binary, _/binary>> = KnownHosts2,
359
360    %% Check that there are exactly two NLs:
361    2 = lists:foldl(fun($\n, Sum) -> Sum + 1;
362                       (_,   Sum) -> Sum
363                    end, 0, binary_to_list(KnownHosts2)),
364
365    %% Check that at least one NL terminates both two lines:
366    <<_:(Sz1-1)/binary, $\n, _:(Sz2-Sz1-1)/binary, $\n>> = KnownHosts2.
367
368
369%%%----------------------------------------------------------------
370try_connect({skip,Reson}) ->
371    {skip,Reson};
372try_connect(Config) ->
373    SystemDir = proplists:get_value(system_dir, Config),
374    UserDir = proplists:get_value(user_dir, Config),
375    ClientOpts = proplists:get_value(client_opts, Config, []),
376    DaemonOpts = proplists:get_value(daemon_opts, Config, []),
377
378    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
379					     {user_dir, UserDir}
380                                             | DaemonOpts]),
381
382    C = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
383                                          {silently_accept_hosts, true},
384                                          {user_interaction, false}
385                                          | ClientOpts]),
386    ssh:close(C),
387    ssh:stop_daemon(Pid).
388
389%%%----------------------------------------------------------------
390%%% Local ---------------------------------------------------------
391%%%----------------------------------------------------------------
392setup_user_system_dir(ClientAlg, ServerAlg, Config) ->
393    case supported(public_keys, ClientAlg) andalso supported(public_keys, ServerAlg) of
394        true ->
395            PrivDir = proplists:get_value(priv_dir, Config),
396            KeySrcDir = proplists:get_value(key_src_dir, Config),
397            Fmt = proplists:get_value(fmt, Config),
398
399            System = lists:concat(["system_", ClientAlg, "_", ServerAlg, "_", Fmt]),
400            SystemDir = filename:join(PrivDir, System),
401            file:make_dir(SystemDir),
402
403            User   = lists:concat(["user_", ClientAlg, "_", ServerAlg, "_", Fmt]),
404            UserDir   = filename:join(PrivDir, User),
405            file:make_dir(UserDir),
406
407            HostSrcFile = filename:join(KeySrcDir, file(host,ServerAlg)),
408            HostDstFile = filename:join(SystemDir, file(host,ServerAlg)),
409
410            UserSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)),
411            UserDstFile = filename:join(UserDir, file(user,ClientAlg)),
412
413            UserPubSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)++".pub"),
414            AuthorizedKeys = filename:join(UserDir, "authorized_keys"),
415
416            try
417                {ok,_} = file:copy(UserSrcFile, UserDstFile),
418                {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
419                {ok,_} = file:copy(HostSrcFile, HostDstFile)
420            of
421                _ ->
422                    ModAlgs = [{modify_algorithms,
423                                [{append,[{public_key,
424                                           lists:usort([alg(ClientAlg),
425                                                        alg(ServerAlg)])}]}]}
426                              ],
427                    [{system_dir,SystemDir},
428                     {user_dir,UserDir}
429                     | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
430            catch
431                error:{badmatch,{error,enoent}}:S ->
432                    ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]),
433                    {skip, no_key_file_found}
434            end;
435
436        false ->
437            {skip, unsupported_algorithm}
438    end.
439
440%%%----------------------------------------------------------------
441file(host, dsa)     -> "ssh_host_dsa_key";
442file(host, ecdsa)   -> "ssh_host_ecdsa_key";
443file(host, ed25519) -> "ssh_host_ed25519_key";
444file(host, ed448)   -> "ssh_host_ed448_key";
445file(host, rsa)     -> "ssh_host_rsa_key";
446file(user, dsa)     -> "id_dsa";
447file(user, ecdsa)   -> "id_ecdsa";
448file(user, ed25519) -> "id_ed25519";
449file(user, ed448)   -> "id_ed448";
450file(user, rsa)     -> "id_rsa".
451
452alg(dsa)     -> 'ssh-dss';
453alg(ecdsa)   -> 'ecdsa-sha2-nistp256';
454alg(ed25519) -> 'ssh-ed25519';
455alg(ed448)   -> 'ssh-ed448';
456alg(rsa)     -> 'ssh-rsa'.
457
458
459supported(public_keys, rsa) ->     supported(public_key, 'ssh-rsa') orelse
460                                       supported(public_key, 'rsa-sha2-256') orelse
461                                       supported(public_key, 'rsa-sha2-521');
462supported(public_keys, dsa) ->     supported(public_key, 'ssh-dss');
463supported(public_keys, ecdsa) ->   supported(public_key, 'ecdsa-sha2-nistp256') orelse
464                                       supported(public_key, 'ecdsa-sha2-nistp384') orelse
465                                       supported(public_key, 'ecdsa-sha2-nistp521');
466supported(public_keys, ed448) ->   supported(public_key, 'ssh-ed448');
467supported(public_keys, ed25519) -> supported(public_key, 'ssh-ed25519');
468supported(Type, Alg) ->
469    case proplists:get_value(Type,ssh_transport:supported_algorithms()) of
470        undefined ->
471            lists:member(Alg, crypto:supports(Type));
472        L ->
473            lists:member(Alg, L)
474    end.
475