From 155763771ba8e581cec43732cf86fa781bbdd773 Mon Sep 17 00:00:00 2001 From: Niclas Eklund Date: Thu, 10 Feb 2011 10:14:22 +0100 Subject: Altering SSH test keys. --- lib/ssh/test/ssh_basic_SUITE.erl | 176 +++++----- lib/ssh/test/ssh_sftp_SUITE.erl | 14 +- lib/ssh/test/ssh_sftpd_SUITE.erl | 9 +- lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl | 3 +- lib/ssh/test/ssh_test_lib.erl | 497 ++++++++++++++++++++++++++++- lib/ssh/test/ssh_to_openssh_SUITE.erl | 108 +++---- 6 files changed, 629 insertions(+), 178 deletions(-) (limited to 'lib/ssh') diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 2c0fd882a0..e801664ff2 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -76,7 +76,7 @@ end_per_suite(Config) -> %% Description: Initialization before each test case %%-------------------------------------------------------------------- init_per_testcase(_TestCase, Config) -> - rename_known_hosts(backup), + ssh_test_lib:known_hosts(backup), ssh:start(), Config. @@ -90,7 +90,7 @@ init_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- end_per_testcase(_TestCase, _Config) -> ssh:stop(), - rename_known_hosts(restore), + ssh_test_lib:known_hosts(restore), ok. %%-------------------------------------------------------------------- @@ -117,6 +117,16 @@ end_per_group(_GroupName, Config) -> %% Test cases starts here. %%-------------------------------------------------------------------- +sign_and_verify_rsa(doc) -> + ["Test api function ssh:sign_data and ssh:verify_data"]; + +sign_and_verify_rsa(suite) -> + []; +sign_and_verify_rsa(Config) when is_list(Config) -> + Data = ssh:sign_data(<<"correct data">>, "ssh-rsa"), + ok = ssh:verify_data(<<"correct data">>, Data, "ssh-rsa"), + {error,invalid_signature} = ssh:verify_data(<<"incorrect data">>, Data,"ssh-rsa"). + exec(doc) -> ["Test api function ssh_connection:exec"]; @@ -127,13 +137,11 @@ exec(suite) -> exec(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = ?config(data_dir, Config), - Host = ssh_test_lib:hostname(), - Port = ssh_test_lib:inet_port(), - {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, - {failfun, fun ssh_test_lib:failfun/2}]), - {ok, ConnectionRef} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user_interaction, false}]), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_interaction, false}]), {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId0, "1+1.", infinity), @@ -171,15 +179,13 @@ exec_compressed(suite) -> exec_compressed(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = ?config(data_dir, Config), - Host = ssh_test_lib:hostname(), - Port = ssh_test_lib:inet_port(), - {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, - {compression, zlib}, - {failfun, fun ssh_test_lib:failfun/2}]), - - {ok, ConnectionRef} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user_interaction, false}]), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {compression, zlib}, + {failfun, fun ssh_test_lib:failfun/2}]), + + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_interaction, false}]), {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId, "1+1.", infinity), @@ -204,9 +210,8 @@ shell(suite) -> shell(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = ?config(data_dir, Config), - Port = ssh_test_lib:inet_port(), - {ok, _Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, - {failfun, fun ssh_test_lib:failfun/2}]), + {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), test_server:sleep(500), IO = ssh_test_lib:start_io_server(), @@ -265,21 +270,20 @@ shell(Config) when is_list(Config) -> end. %%-------------------------------------------------------------------- -daemon_allready_started(doc) -> +daemon_already_started(doc) -> ["Test that get correct error message if you try to start a daemon", - "on an adress that allready runs a daemon see also seq10667" ]; + "on an adress that already runs a daemon see also seq10667" ]; -daemon_allready_started(suite) -> +daemon_already_started(suite) -> []; -daemon_allready_started(Config) when is_list(Config) -> +daemon_already_started(Config) when is_list(Config) -> SystemDir = ?config(data_dir, Config), - Port = ssh_test_lib:inet_port(), - {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, - {failfun, fun ssh_test_lib:failfun/2}]), - {error, eaddrinuse} = ssh:daemon(Port, [{system_dir, SystemDir}, - {failfun, - fun ssh_test_lib:failfun/2}]), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + {error, eaddrinuse} = ssh_test_lib:daemon(Host, Port, [{system_dir, SystemDir}, + {failfun, + fun ssh_test_lib:failfun/2}]), ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- @@ -290,26 +294,23 @@ server_password_option(suite) -> server_password_option(Config) when is_list(Config) -> UserDir = ?config(data_dir, Config), % to make sure we don't use SysDir = ?config(data_dir, Config), % public-key-auth - Port = ssh_test_lib:inet_port(), - {ok, Pid} = - ssh:daemon(Port, [{system_dir, SysDir}, - {password, "morot"}]), - Host = ssh_test_lib:hostname(), - - {ok, ConnectionRef} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {password, "morot"}]), + + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), {error, Reason} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "vego"}, - {password, "foo"}, - {user_interaction, false}, - {user_dir, UserDir}]), - - test_server:format("Test of wrong pasword: Error msg: ~p ~n", [Reason]), + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "vego"}, + {password, "foo"}, + {user_interaction, false}, + {user_dir, UserDir}]), + + test_server:format("Test of wrong password: Error msg: ~p ~n", [Reason]), ssh:close(ConnectionRef), ssh:stop_daemon(Pid). @@ -323,39 +324,36 @@ server_userpassword_option(suite) -> server_userpassword_option(Config) when is_list(Config) -> UserDir = ?config(data_dir, Config), % to make sure we don't use SysDir = ?config(data_dir, Config), % public-key-auth - Port = ssh_test_lib:inet_port(), - {ok, Pid} = - ssh:daemon(Port, [{system_dir, SysDir}, - {user_passwords, [{"vego", "morot"}]}]), - Host = ssh_test_lib:hostname(), - - {ok, ConnectionRef} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "vego"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_passwords, [{"vego", "morot"}]}]), + + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "vego"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), ssh:close(ConnectionRef), {error, Reason0} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), - + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), + test_server:format("Test of user foo that does not exist. " "Error msg: ~p ~n", [Reason0]), {error, Reason1} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "vego"}, - {password, "foo"}, - {user_interaction, false}, - {user_dir, UserDir}]), - test_server:format("Test of wrong Pasword. " + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "vego"}, + {password, "foo"}, + {user_interaction, false}, + {user_dir, UserDir}]), + test_server:format("Test of wrong Password. " "Error msg: ~p ~n", [Reason1]), - + ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- @@ -366,41 +364,27 @@ known_hosts(suite) -> known_hosts(Config) when is_list(Config) -> SystemDir = ?config(data_dir, Config), UserDir = ?config(priv_dir, Config), - Port = ssh_test_lib:inet_port(), - {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, - {failfun, fun ssh_test_lib:failfun/2}]), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), KnownHosts = filename:join(UserDir, "known_hosts"), file:delete(KnownHosts), {error, enoent} = file:read_file(KnownHosts), - Host = ssh_test_lib:hostname(), - {ok, ConnectionRef} = - ssh:connect(Host, Port, [{user_dir, UserDir}, - {user_interaction, false}, - silently_accept_hosts]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{user_dir, UserDir}, + {user_interaction, false}, + silently_accept_hosts]), {ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity), ok = ssh:close(ConnectionRef), {ok, Binary} = file:read_file(KnownHosts), Lines = string:tokens(binary_to_list(Binary), "\n"), [Line] = Lines, - {ok, Hostname} = inet:gethostname(), [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "), - [Hostname, _Ip] = string:tokens(HostAndIp, ","), + [Host, _Ip] = string:tokens(HostAndIp, ","), "ssh-" ++ _ = Alg, ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- - -rename_known_hosts(BR) -> - KnownHosts = ssh_file:file_name(user, "known_hosts", []), - B = KnownHosts ++ "xxx", - case BR of - backup -> - file:rename(KnownHosts, B); - restore -> - file:delete(KnownHosts), - file:rename(B, KnownHosts) - end. diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index 0c4a7f3b3f..123a12773b 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -103,13 +103,13 @@ init_per_testcase(_Case, Config) -> {ok, ChannelPid, Connection} -> {ChannelPid, Connection}; _Error -> - {ok, _Sftpd} = - ssh:daemon(?SFPD_PORT, - [{system_dir, SysDir}, - {user_passwords, - [{?USER, ?PASSWD}]}, - {failfun, - fun ssh_test_lib:failfun/2}]), + {_Sftpd, _Host, _Port} = + ssh_test_lib:daemon(Host, ?SFPD_PORT, + [{system_dir, SysDir}, + {user_passwords, + [{?USER, ?PASSWD}]}, + {failfun, + fun ssh_test_lib:failfun/2}]), Result = (catch ssh_sftp:start_channel(Host, ?SFPD_PORT, [{user, ?USER}, {password, ?PASSWD}, diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl index 1afc206148..f5ed668fa6 100644 --- a/lib/ssh/test/ssh_sftpd_SUITE.erl +++ b/lib/ssh/test/ssh_sftpd_SUITE.erl @@ -93,16 +93,15 @@ init_per_testcase(TestCase, Config) -> SysDir = ?config(data_dir, Config), {ok, Sftpd} = ssh_sftpd:listen(?SFPD_PORT, [{system_dir, SysDir}, - {user_passwords,[{?USER, ?PASSWD}]}]), - - Host = ssh_test_lib:hostname(), - {ok, Cm} = ssh:connect(Host, ?SFPD_PORT, + {user_passwords,[{?USER, ?PASSWD}]}]), + + Cm = ssh_test_lib:connect(?SFPD_PORT, [{silently_accept_hosts, true}, {user, ?USER}, {password, ?PASSWD}]), {ok, Channel} = ssh_connection:session_channel(Cm, ?XFER_WINDOW_SIZE, ?XFER_PACKET_SIZE, ?TIMEOUT), - + success = ssh_connection:subsystem(Cm, Channel, "sftp", ?TIMEOUT), ProtocolVer = case atom_to_list(TestCase) of diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl index c7107635c1..db23a98225 100644 --- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl +++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl @@ -115,9 +115,8 @@ init_per_testcase(TestCase, Config) -> {system_dir, DataDir}] end, - {ok, Sftpd} = ssh:daemon(any, ?SSHD_PORT, Options), + {Sftpd, Host, _Port} = ssh_test_lib:daemon(any, ?SSHD_PORT, Options), - Host = ssh_test_lib:hostname(), {ok, ChannelPid, Connection} = ssh_sftp:start_channel(Host, ?SSHD_PORT, [{silently_accept_hosts, true}, diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index 2eb19cec22..c237e1ba5d 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -24,12 +24,54 @@ %% Note: This directive should only be used in test suites. -compile(export_all). +-include_lib("public_key/include/public_key.hrl"). +-include("test_server.hrl"). +-include("test_server_line.hrl"). + +-define(TIMEOUT, 50000). +-define(SSH_DEFAULT_PORT, 22). + +connect(Options) -> + connect(hostname(), inet_port(), Options). + +connect(Port, Options) when is_integer(Port) -> + connect(hostname(), Port, Options); +connect(Host, Options) -> + connect(Host, inet_port(), Options). + +connect(Host, Port, Options) -> + case ssh:connect(Host, Port, Options) of + {ok, ConnectionRef} -> + ConnectionRef; + Error -> + Error + end. + +daemon(Options) -> + daemon(any, inet_port(), Options). + +daemon(Port, Options) when is_integer(Port) -> + daemon(any, Port, Options); +daemon(Host, Options) -> + daemon(Host, inet_port(), Options). + +daemon(Host, Port, Options) -> + case ssh:daemon(Host, Port, Options) of + {ok, Pid} -> + {Pid, Host, Port}; + Error -> + Error + end. + + + + start_shell(Port, IOServer) -> spawn_link(?MODULE, init_shell, [Port, IOServer]). init_shell(Port, IOServer) -> - Host = ssh_test_lib:hostname(), - UserDir = ssh_test_lib:get_user_dir(), + Host = hostname(), + UserDir = get_user_dir(), Options = [{user_interaction, false}, {silently_accept_hosts, true}] ++ UserDir, group_leader(IOServer, self()), @@ -123,9 +165,9 @@ receive_exec_end(ConnectionRef, ChannelId) -> receive_exec_result(Data, ConnectionRef, ChannelId) -> Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}}, Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}}, - expected = ssh_test_lib:receive_exec_result(Data), - expected = ssh_test_lib:receive_exec_result(Eof), - expected = ssh_test_lib:receive_exec_result(Closed). + expected = receive_exec_result(Data), + expected = receive_exec_result(Eof), + expected = receive_exec_result(Closed). inet_port()-> @@ -164,6 +206,18 @@ hostname() -> {ok,Host} = inet:gethostname(), Host. +known_hosts(BR) -> + KnownHosts = ssh_file:file_name(user, "known_hosts", []), + B = KnownHosts ++ "xxx", + case BR of + backup -> + file:rename(KnownHosts, B); + restore -> + file:delete(KnownHosts), + file:rename(B, KnownHosts) + end. + + save_known_hosts(PrivDir) -> Src = ssh_file:file_name(user, "known_hosts", []), Dst = filename:join(PrivDir, "kh_save"), @@ -187,3 +241,434 @@ get_user_dir() -> _ -> [] end. + + +%% Create certificates. +make_dsa_cert(Config) -> + + {ServerCaCertFile, ServerCertFile, ServerKeyFile} = make_dsa_cert_files("server", Config), + {ClientCaCertFile, ClientCertFile, ClientKeyFile} = make_dsa_cert_files("client", Config), + [{server_dsa_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ServerCaCertFile}, + {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, + {server_dsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ClientCaCertFile}, + {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, + {verify, verify_peer}]}, + {client_dsa_opts, [{ssl_imp, new},{reuseaddr, true}, + {cacertfile, ClientCaCertFile}, + {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]} + | Config]. + + + +make_dsa_cert_files(RoleStr, Config) -> + CaInfo = {CaCert, _} = make_cert([{key, dsa}]), + {Cert, CertKey} = make_cert([{key, dsa}, {issuer, CaInfo}]), + CaCertFile = filename:join(["/home/nick/trash/ssh/", + RoleStr, "dsa_cacerts.pem"]), + CertFile = filename:join(["/home/nick/trash/ssh/", + RoleStr, "dsa_cert.pem"]), + KeyFile = filename:join(["/home/nick/trash/ssh/", + RoleStr, "dsa_key.pem"]), +%% CaCertFile = filename:join([?config(priv_dir, Config), +%% RoleStr, "dsa_cacerts.pem"]), +%% CertFile = filename:join([?config(priv_dir, Config), +%% RoleStr, "dsa_cert.pem"]), +%% KeyFile = filename:join([?config(priv_dir, Config), +%% RoleStr, "dsa_key.pem"]), + + der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]), + der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]), + der_to_pem(KeyFile, [CertKey]), + {CaCertFile, CertFile, KeyFile}. + + +%%-------------------------------------------------------------------- +%% Create and return a der encoded certificate +%% Option Default +%% ------------------------------------------------------- +%% digest sha1 +%% validity {date(), date() + week()} +%% version 3 +%% subject [] list of the following content +%% {name, Name} +%% {email, Email} +%% {city, City} +%% {state, State} +%% {org, Org} +%% {org_unit, OrgUnit} +%% {country, Country} +%% {serial, Serial} +%% {title, Title} +%% {dnQualifer, DnQ} +%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) +%% (obs IssuerKey migth be {Key, Password} +%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key +%% +%% +%% (OBS: The generated keys are for testing only) +%% make_cert([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} +%%-------------------------------------------------------------------- +make_cert(Opts) -> + SubjectPrivateKey = get_key(Opts), + {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), + Cert = public_key:pkix_sign(TBSCert, IssuerKey), + true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok + {Cert, encode_key(SubjectPrivateKey)}. + +%%-------------------------------------------------------------------- +%% Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" +%% write_pem(::string(), ::string(), {Cert,Key}) -> ok +%%-------------------------------------------------------------------- +write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> + ok = der_to_pem(filename:join(Dir, FileName ++ ".pem"), + [{'Certificate', Cert, not_encrypted}]), + ok = der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). + +%%-------------------------------------------------------------------- +%% Creates a rsa key (OBS: for testing only) +%% the size are in bytes +%% gen_rsa(::integer()) -> {::atom(), ::binary(), ::opaque()} +%%-------------------------------------------------------------------- +gen_rsa(Size) when is_integer(Size) -> + Key = gen_rsa2(Size), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% Creates a dsa key (OBS: for testing only) +%% the sizes are in bytes +%% gen_dsa(::integer()) -> {::atom(), ::binary(), ::opaque()} +%%-------------------------------------------------------------------- +gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> + Key = gen_dsa2(LSize, NSize), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% Verifies cert signatures +%% verify_signature(::binary(), ::tuple()) -> ::boolean() +%%-------------------------------------------------------------------- +verify_signature(DerEncodedCert, DerKey, _KeyParams) -> + Key = decode_key(DerKey), + case Key of + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> + public_key:pkix_verify(DerEncodedCert, + #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}); + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> + public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_key(Opts) -> + case proplists:get_value(key, Opts) of + undefined -> make_key(rsa, Opts); + rsa -> make_key(rsa, Opts); + dsa -> make_key(dsa, Opts); + Key -> + Password = proplists:get_value(password, Opts, no_passwd), + decode_key(Key, Password) + end. + +decode_key({Key, Pw}) -> + decode_key(Key, Pw); +decode_key(Key) -> + decode_key(Key, no_passwd). + + +decode_key(#'RSAPublicKey'{} = Key,_) -> + Key; +decode_key(#'RSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'DSAPrivateKey'{} = Key,_) -> + Key; +decode_key(PemEntry = {_,_,_}, Pw) -> + public_key:pem_entry_decode(PemEntry, Pw); +decode_key(PemBin, Pw) -> + [KeyInfo] = public_key:pem_decode(PemBin), + decode_key(KeyInfo, Pw). + +encode_key(Key = #'RSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), + {'RSAPrivateKey', list_to_binary(Der), not_encrypted}; +encode_key(Key = #'DSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), + {'DSAPrivateKey', list_to_binary(Der), not_encrypted}. + +make_tbs(SubjectKey, Opts) -> + Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), + + IssuerProp = proplists:get_value(issuer, Opts, true), + {Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey), + + {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), + + SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, + parameters = Parameters}, + Subject = case IssuerProp of + true -> %% Is a Root Ca + Issuer; + _ -> + subject(proplists:get_value(subject, Opts),false) + end, + + {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + signature = SignAlgo, + issuer = Issuer, + validity = validity(Opts), + subject = Subject, + subjectPublicKeyInfo = publickey(SubjectKey), + version = Version, + extensions = extensions(Opts) + }, IssuerKey}. + +issuer(true, Opts, SubjectKey) -> + %% Self signed + {subject(proplists:get_value(subject, Opts), true), SubjectKey}; +issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) -> + {issuer_der(Issuer), decode_key(IssuerKey)}; +issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) -> + {ok, [{cert, Cert, _}|_]} = pem_to_der(File), + {issuer_der(Cert), decode_key(IssuerKey)}. + +issuer_der(Issuer) -> + Decoded = public_key:pkix_decode_cert(Issuer, otp), + #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, + #'OTPTBSCertificate'{subject=Subject} = Tbs, + Subject. + +subject(undefined, IsRootCA) -> + User = if IsRootCA -> "RootCA"; true -> os:getenv("USER") end, + Opts = [{email, User ++ "@erlang.org"}, + {name, User}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"}], + subject(Opts); +subject(Opts, _) -> + subject(Opts). + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +%% Fill in the blanks +subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> {?'id-emailAddress', Email}; +subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> Other. + + +extensions(Opts) -> + case proplists:get_value(extensions, Opts, []) of + false -> + asn1_NOVALUE; + Exts -> + lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) + end. + +default_extensions(Exts) -> + Def = [{key_usage,undefined}, + {subject_altname, undefined}, + {issuer_altname, undefined}, + {basic_constraints, default}, + {name_constraints, undefined}, + {policy_constraints, undefined}, + {ext_key_usage, undefined}, + {inhibit_any, undefined}, + {auth_key_id, undefined}, + {subject_key_id, undefined}, + {policy_mapping, undefined}], + Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, + critical=true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + + +publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}. + +validity(Opts) -> + DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'sha1WithRSAEncryption'; + sha512 -> ?'sha512WithRSAEncryption'; + sha384 -> ?'sha384WithRSAEncryption'; + sha256 -> ?'sha256WithRSAEncryption'; + md5 -> ?'md5WithRSAEncryption'; + md2 -> ?'md2WithRSAEncryption' + end, + {Type, 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}. + +make_key(rsa, _Opts) -> + %% (OBS: for testing only) + gen_rsa2(64); +make_key(dsa, _Opts) -> + gen_dsa2(128, 20). %% Bytes i.e. {1024, 160} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% RSA key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, + 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). + +gen_rsa2(Size) -> + P = prime(Size), + Q = prime(Size), + N = P*Q, + Tot = (P - 1) * (Q - 1), + [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), + {D1,D2} = extended_gcd(E, Tot), + D = erlang:max(D1,D2), + case D < E of + true -> + gen_rsa2(Size); + false -> + {Co1,Co2} = extended_gcd(Q, P), + Co = erlang:max(Co1,Co2), + #'RSAPrivateKey'{version = 'two-prime', + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + exponent1 = D rem (P-1), + exponent2 = D rem (Q-1), + coefficient = Co + } + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DSA key generation (OBS: for testing only) +%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +%% and the fips_186-3.pdf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +gen_dsa2(LSize, NSize) -> + Q = prime(NSize), %% Choose N-bit prime Q + X0 = prime(LSize), + P0 = prime((LSize div 2) +1), + + %% Choose L-bit prime modulus P such that p-1 is a multiple of q. + case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of + error -> + gen_dsa2(LSize, NSize); + P -> + G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. + %% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used. + + X = prime(20), %% Choose x by some random method, where 0 < x < q. + Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p. + + #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X} + end. + +%% See fips_186-3.pdf +dsa_search(T, P0, Q, Iter) when Iter > 0 -> + P = 2*T*Q*P0 + 1, + case is_prime(crypto:mpint(P), 50) of + true -> P; + false -> dsa_search(T+1, P0, Q, Iter-1) + end; +dsa_search(_,_,_,_) -> + error. + + +%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prime(ByteSize) -> + Rand = odd_rand(ByteSize), + crypto:erlint(prime_odd(Rand, 0)). + +prime_odd(Rand, N) -> + case is_prime(Rand, 50) of + true -> + Rand; + false -> + NotPrime = crypto:erlint(Rand), + prime_odd(crypto:mpint(NotPrime+2), N+1) + end. + +%% see http://en.wikipedia.org/wiki/Fermat_primality_test +is_prime(_, 0) -> true; +is_prime(Candidate, Test) -> + CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate), + case crypto:mod_exp(CoPrime, Candidate, Candidate) of + CoPrime -> is_prime(Candidate, Test-1); + _ -> false + end. + +odd_rand(Size) -> + Min = 1 bsl (Size*8-1), + Max = (1 bsl (Size*8))-1, + odd_rand(crypto:mpint(Min), crypto:mpint(Max)). + +odd_rand(Min,Max) -> + Rand = <> = crypto:rand_uniform(Min,Max), + BitSkip = (Sz+4)*8-1, + case Rand of + Odd = <<_:BitSkip, 1:1>> -> Odd; + Even = <<_:BitSkip, 0:1>> -> + crypto:mpint(crypto:erlint(Even)+1) + end. + +extended_gcd(A, B) -> + case A rem B of + 0 -> + {0, 1}; + N -> + {X, Y} = extended_gcd(B, N), + {Y, X-Y*(A div B)} + end. + +pem_to_der(File) -> + {ok, PemBin} = file:read_file(File), + public_key:pem_decode(PemBin). + +der_to_pem(File, Entries) -> + PemBin = public_key:pem_encode(Entries), + file:write_file(File, PemBin). diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index 0c15c067a8..cf3279df75 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -159,10 +159,8 @@ erlang_client_openssh_server_exec(suite) -> []; erlang_client_openssh_server_exec(Config) when is_list(Config) -> - Host = ssh_test_lib:hostname(), - {ok, ConnectionRef} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, - {user_interaction, false}]), + ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}]), {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId0, "echo testing", infinity), @@ -203,11 +201,9 @@ erlang_client_openssh_server_exec_compressed(suite) -> []; erlang_client_openssh_server_exec_compressed(Config) when is_list(Config) -> - Host = ssh_test_lib:hostname(), - {ok, ConnectionRef} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, - {user_interaction, false}, - {compression, zlib}]), + ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}, + {compression, zlib}]), {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), success = ssh_connection:exec(ConnectionRef, ChannelId, "echo testing", infinity), @@ -231,13 +227,11 @@ erlang_server_openssh_client_exec(suite) -> []; erlang_server_openssh_client_exec(Config) when is_list(Config) -> - SytemDir = ?config(data_dir, Config), - Host = ssh_test_lib:hostname(), - Port = ssh_test_lib:inet_port(), - - {ok, Pid} = ssh:daemon(Port, [{system_dir, SytemDir}, - {failfun, fun ssh_test_lib:failfun/2}]), - + SystemDir = ?config(data_dir, Config), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + test_server:sleep(500), @@ -262,12 +256,10 @@ erlang_server_openssh_client_exec_compressed(suite) -> []; erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) -> - SytemDir = ?config(data_dir, Config), - Host = ssh_test_lib:hostname(), - Port = ssh_test_lib:inet_port(), - {ok, Pid} = ssh:daemon(Port, [{system_dir, SytemDir}, - {compression, zlib}, - {failfun, fun ssh_test_lib:failfun/2}]), + SystemDir = ?config(data_dir, Config), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {compression, zlib}, + {failfun, fun ssh_test_lib:failfun/2}]), test_server:sleep(500), @@ -292,10 +284,9 @@ erlang_client_openssh_server_setenv(suite) -> []; erlang_client_openssh_server_setenv(Config) when is_list(Config) -> - Host = ssh_test_lib:hostname(), - {ok, ConnectionRef} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, - {user_interaction, false}]), + ConnectionRef = + ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user_interaction, false}]), {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), Env = case ssh_connection:setenv(ConnectionRef, ChannelId, @@ -340,16 +331,14 @@ erlang_client_openssh_server_publickey_rsa(Config) when is_list(Config) -> {ok,[[Home]]} = init:get_argument(home), SrcDir = filename:join(Home, ".ssh"), UserDir = ?config(priv_dir, Config), - Host = ssh_test_lib:hostname(), - case ssh_test_lib:copyfile(SrcDir, UserDir, "id_rsa") of {ok, _} -> - {ok, ConnectionRef} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, - [{user_dir, UserDir}, - {public_key_alg, ssh_rsa}, - {user_interaction, false}, - silently_accept_hosts]), + ConnectionRef = + ssh_test_lib:connect(?SSH_DEFAULT_PORT, + [{user_dir, UserDir}, + {public_key_alg, ssh_rsa}, + {user_interaction, false}, + silently_accept_hosts]), {ok, Channel} = ssh_connection:session_channel(ConnectionRef, infinity), ok = ssh_connection:close(ConnectionRef, Channel), @@ -368,15 +357,14 @@ erlang_client_openssh_server_publickey_dsa(Config) when is_list(Config) -> {ok,[[Home]]} = init:get_argument(home), SrcDir = filename:join(Home, ".ssh"), UserDir = ?config(priv_dir, Config), - Host = ssh_test_lib:hostname(), case ssh_test_lib:copyfile(SrcDir, UserDir, "id_dsa") of {ok, _} -> - {ok, ConnectionRef} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, - [{user_dir, UserDir}, - {public_key_alg, ssh_dsa}, - {user_interaction, false}, - silently_accept_hosts]), + ConnectionRef = + ssh_test_lib:connect(?SSH_DEFAULT_PORT, + [{user_dir, UserDir}, + {public_key_alg, ssh_dsa}, + {user_interaction, false}, + silently_accept_hosts]), {ok, Channel} = ssh_connection:session_channel(ConnectionRef, infinity), ok = ssh_connection:close(ConnectionRef, Channel), @@ -394,13 +382,11 @@ erlang_server_openssh_client_pulic_key_dsa(suite) -> []; erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) -> - SytemDir = ?config(data_dir, Config), - Host = ssh_test_lib:hostname(), - Port = ssh_test_lib:inet_port(), - {ok, Pid} = ssh:daemon(Port, [{system_dir, SytemDir}, - {public_key_alg, ssh_dsa}, - {failfun, fun ssh_test_lib:failfun/2}]), - + SystemDir = ?config(data_dir, Config), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {public_key_alg, ssh_dsa}, + {failfun, fun ssh_test_lib:failfun/2}]), + test_server:sleep(500), Cmd = "ssh -p " ++ integer_to_list(Port) ++ @@ -426,15 +412,13 @@ erlang_client_openssh_server_password(suite) -> erlang_client_openssh_server_password(Config) when is_list(Config) -> %% to make sure we don't public-key-auth UserDir = ?config(data_dir, Config), - Host = ssh_test_lib:hostname(), - {error, Reason0} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), - + ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), + test_server:format("Test of user foo that does not exist. " "Error msg: ~p~n", [Reason0]), @@ -443,12 +427,12 @@ erlang_client_openssh_server_password(Config) when is_list(Config) -> case length(string:tokens(User, " ")) of 1 -> {error, Reason1} = - ssh:connect(Host, ?SSH_DEFAULT_PORT, - [{silently_accept_hosts, true}, - {user, User}, - {password, "foo"}, - {user_interaction, false}, - {user_dir, UserDir}]), + ssh_test_lib:connect(?SSH_DEFAULT_PORT, + [{silently_accept_hosts, true}, + {user, User}, + {password, "foo"}, + {user_interaction, false}, + {user_dir, UserDir}]), test_server:format("Test of wrong Pasword. " "Error msg: ~p~n", [Reason1]); _ -> -- cgit v1.2.3