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