diff options
author | Hans Nilsson <[email protected]> | 2014-12-05 11:04:08 +0100 |
---|---|---|
committer | Hans Nilsson <[email protected]> | 2014-12-17 10:24:34 +0100 |
commit | c60f63c717eb488abfe9afacf6e1b6461e521aae (patch) | |
tree | 4f2daf106d7b3b4f16b41c87dadd36b53e73035a /lib/eldap/test | |
parent | bb1894e25ce539e3d2417008f3f714a8a09ed045 (diff) | |
download | otp-c60f63c717eb488abfe9afacf6e1b6461e521aae.tar.gz otp-c60f63c717eb488abfe9afacf6e1b6461e521aae.tar.bz2 otp-c60f63c717eb488abfe9afacf6e1b6461e521aae.zip |
eldap: Adds ssl to connections test suite
Diffstat (limited to 'lib/eldap/test')
-rw-r--r-- | lib/eldap/test/Makefile | 4 | ||||
-rw-r--r-- | lib/eldap/test/eldap_basic_SUITE.erl | 51 | ||||
-rw-r--r-- | lib/eldap/test/eldap_basic_SUITE_data/RAND | bin | 0 -> 512 bytes | |||
-rw-r--r-- | lib/eldap/test/eldap_connections_SUITE.erl | 167 | ||||
-rw-r--r-- | lib/eldap/test/eldap_connections_SUITE_data/RAND | bin | 0 -> 512 bytes | |||
-rw-r--r-- | lib/eldap/test/eldap_connections_SUITE_data/certs/README | 1 | ||||
-rw-r--r-- | lib/eldap/test/make_certs.erl | 357 |
7 files changed, 444 insertions, 136 deletions
diff --git a/lib/eldap/test/Makefile b/lib/eldap/test/Makefile index 24e71cebaa..a4b65fef88 100644 --- a/lib/eldap/test/Makefile +++ b/lib/eldap/test/Makefile @@ -29,7 +29,9 @@ INCLUDES= -I. -I ../include MODULES= \ eldap_connections_SUITE \ - eldap_basic_SUITE + eldap_basic_SUITE \ + make_certs + ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index 0ffb98c113..93d83d1f4e 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -64,33 +64,16 @@ groups() -> ]. init_per_suite(Config) -> - SSL_started = - try ssl:start() - of - ok -> true; - {error,{already_started,ssl}} -> true - catch - Error:Reason -> - ct:comment("ssl failed to start"), - ct:log("init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]), - false - end, - - case SSL_started of - true -> make_certs:all("/dev/null", - filename:join(?config(data_dir,Config), "certs")); - false -> ok - end, - + SSL_available = init_ssl_certs_et_al(Config), LDAP_server = find_first_server(false, [{config,eldap_server}, {config,ldap_server}, {"localhost",9876}]), LDAPS_server = - case SSL_started of + case SSL_available of true -> find_first_server(true, [{config,ldaps_server}, {"localhost",9877}]); false -> undefined end, - [{ssl_available, SSL_started}, + [{ssl_available, SSL_available}, {ldap_server, LDAP_server}, {ldaps_server, LDAPS_server} | Config]. @@ -635,3 +618,31 @@ supported_extension(OID, Config) -> _Ok = eldap:close(H), false end. + +%%%---------------------------------------------------------------- +init_ssl_certs_et_al(Config) -> + try ssl:start() + of + R when R==ok ; R=={error,{already_started,ssl}} -> + try make_certs:all("/dev/null", + filename:join(?config(data_dir,Config), "certs")) + of + {ok,_} -> true; + Other -> + ct:comment("make_certs failed"), + ct:log("make_certs failed ~p", [Other]), + false + catch + C:E -> + ct:comment("make_certs crashed"), + ct:log("make_certs failed ~p:~p", [C,E]), + false + end; + _ -> + false + catch + Error:Reason -> + ct:comment("ssl failed to start"), + ct:log("init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]), + false + end. diff --git a/lib/eldap/test/eldap_basic_SUITE_data/RAND b/lib/eldap/test/eldap_basic_SUITE_data/RAND Binary files differnew file mode 100644 index 0000000000..70997bd01f --- /dev/null +++ b/lib/eldap/test/eldap_basic_SUITE_data/RAND diff --git a/lib/eldap/test/eldap_connections_SUITE.erl b/lib/eldap/test/eldap_connections_SUITE.erl index c5460fef09..fb859c009b 100644 --- a/lib/eldap/test/eldap_connections_SUITE.erl +++ b/lib/eldap/test/eldap_connections_SUITE.erl @@ -55,18 +55,72 @@ end_per_group(_GroupName, Config) -> groups() -> - [{v4, [], [tcp_connection, tcp_connection_option]}, - {v6, [], [tcp_connection, tcp_connection_option]} + [{v4, [], tests()}, + {v6, [], tests()} ]. +tests() -> + [tcp_connection, + tcp_connection_option, + ssl_connection, + client_side_start_tls_timeout, + client_side_bind_timeout, + client_side_add_timeout, + client_side_search_timeout + ]. -init_per_suite(Config) -> Config. - +init_per_suite(Config) -> + HasSSL = init_ssl_certs_et_al(Config), + [{has_ssl,HasSSL} | Config]. end_per_suite(_Config) -> ok. -init_per_testcase(_TestCase, Config) -> +init_per_testcase(ssl_connection, Config) -> + case ?config(has_ssl,Config) of + true -> + SSL_Port = 9999, + CertFile = filename:join(?config(data_dir,Config), "certs/server/cert.pem"), + KeyFile = filename:join(?config(data_dir,Config), "certs/server/key.pem"), + + Parent = self(), + Listener = spawn_link( + fun() -> + case ssl:listen(SSL_Port, [{certfile, CertFile}, + {keyfile, KeyFile}, + {reuseaddr, true}]) of + {ok,SSL_LSock} -> + Parent ! {ok,self()}, + (fun L() -> + ct:log("ssl server waiting for connections...",[]), + {ok, S} = ssl:transport_accept(SSL_LSock), + ct:log("ssl:transport_accept/1 ok",[]), + ok = ssl:ssl_accept(S), + ct:log("ssl:ssl_accept/1 ok",[]), + L() + end)(); + Other -> + Parent ! {not_ok,Other,self()} + end + end), + receive + {ok,Listener} -> + ct:log("SSL listening to port ~p (process ~p)",[SSL_Port, Listener]), + [{ssl_listener,Listener}, + {ssl_listen_port,SSL_Port}, + {ssl_connect_opts,[]} + | Config]; + {no_ok,SSL_Other,Listener} -> + ct:log("ssl:listen on port ~p failed: ~p",[SSL_Port,SSL_Other]), + {fail, "ssl:listen/2 failed"} + after 5000 -> + {fail, "Waiting for ssl:listen timeout"} + end; + false -> + {skip, "ssl not available"} + end; + +init_per_testcase(_, Config) -> case gen_tcp:listen(0, proplists:get_value(listen_opts,Config)) of {ok,LSock} -> {ok,{_,Port}} = inet:sockname(LSock), @@ -100,6 +154,52 @@ tcp_connection(Config) -> Other -> ct:fail("eldap:open failed: ~p",[Other]) end. +%%%---------------------------------------------------------------- +ssl_connection(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(ssl_listen_port, Config), + Opts = proplists:get_value(connect_opts, Config), + SSLOpts = proplists:get_value(ssl_connect_opts, Config), + case eldap:open([Host], [{port,Port},{ssl,true}, + {timeout,5000}, + {sslopts,SSLOpts}|Opts]) of + {ok,_H} -> ok; + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + +%%%---------------------------------------------------------------- +client_side_add_timeout(Config) -> + client_timeout( + fun(H) -> + eldap:add(H, "cn=Foo Bar,dc=host,dc=ericsson,dc=se", + [{"objectclass", ["person"]}, + {"cn", ["Foo Bar"]}, + {"sn", ["Bar"]}, + {"telephoneNumber", ["555-1232", "555-5432"]}]) + end, Config). + +%%%---------------------------------------------------------------- +client_side_bind_timeout(Config) -> + client_timeout( + fun(H) -> + eldap:simple_bind(H, anon, anon) + end, Config). + +%%%---------------------------------------------------------------- +client_side_search_timeout(Config) -> + client_timeout( + fun(H) -> + eldap:search(H, [{base,"dc=host,dc=ericsson,dc=se"}, + {filter, eldap:present("objectclass")}, + {scope, eldap:wholeSubtree()}]) + end, Config). + +%%%---------------------------------------------------------------- +client_side_start_tls_timeout(Config) -> + client_timeout( + fun(H) -> + eldap:start_tls(H, []) + end, Config). %%%---------------------------------------------------------------- tcp_connection_option(Config) -> @@ -145,3 +245,60 @@ tcp_connection_option(Config) -> Other -> ct:fail("eldap:open failed: ~p",[Other]) end. + + +%%%================================================================ +%%% +%%% Private +%%% + +client_timeout(Fun, Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(listen_port, Config), + Opts = proplists:get_value(connect_opts, Config), + T = 1000, + case eldap:open([Host], [{timeout,T},{port,Port}|Opts]) of + {ok,H} -> + T0 = now(), + {error,{gen_tcp_error,timeout}} = Fun(H), + T_op = diff(T0,now()), + ct:log("Time = ~p, Timeout spec = ~p",[T_op,T]), + if + T_op < T -> + {fail, "Timeout too early"}; + true -> + ok + end; + + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + +diff({M1,S1,U1},{M2,S2,U2}) -> + ( ((M2-M1)*1000 + (S2-S1))*1000 + (U2-U1) ). +%%%---------------------------------------------------------------- +init_ssl_certs_et_al(Config) -> + try ssl:start() + of + R when R==ok ; R=={error,{already_started,ssl}} -> + try make_certs:all("/dev/null", + filename:join(?config(data_dir,Config), "certs")) + of + {ok,_} -> true; + Other -> + ct:comment("make_certs failed"), + ct:log("make_certs failed ~p", [Other]), + false + catch + C:E -> + ct:comment("make_certs crashed"), + ct:log("make_certs failed ~p:~p", [C,E]), + false + end; + _ -> + false + catch + Error:Reason -> + ct:comment("ssl failed to start"), + ct:log("init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]), + false + end. diff --git a/lib/eldap/test/eldap_connections_SUITE_data/RAND b/lib/eldap/test/eldap_connections_SUITE_data/RAND Binary files differnew file mode 100644 index 0000000000..70997bd01f --- /dev/null +++ b/lib/eldap/test/eldap_connections_SUITE_data/RAND diff --git a/lib/eldap/test/eldap_connections_SUITE_data/certs/README b/lib/eldap/test/eldap_connections_SUITE_data/certs/README new file mode 100644 index 0000000000..a7c8e9dc2e --- /dev/null +++ b/lib/eldap/test/eldap_connections_SUITE_data/certs/README @@ -0,0 +1 @@ +See ../../README diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl index f963af180d..15a7e118ff 100644 --- a/lib/eldap/test/make_certs.erl +++ b/lib/eldap/test/make_certs.erl @@ -1,41 +1,89 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2007-2012. 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 %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(make_certs). +-compile([export_all]). --export([all/2]). +%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]). --record(dn, {commonName, +-record(config, {commonName, organizationalUnitName = "Erlang OTP", organizationName = "Ericsson AB", localityName = "Stockholm", countryName = "SE", - emailAddress = "[email protected]"}). + emailAddress = "[email protected]", + default_bits = 2048, + v2_crls = true, + ecc_certs = false, + issuing_distribution_point = false, + crl_port = 8000, + openssl_cmd = "openssl"}). + + +default_config() -> + #config{}. + +make_config(Args) -> + make_config(Args, #config{}). + +make_config([], C) -> + C; +make_config([{organizationalUnitName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{organizationalUnitName = Name}); +make_config([{organizationName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{organizationName = Name}); +make_config([{localityName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{localityName = Name}); +make_config([{countryName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{countryName = Name}); +make_config([{emailAddress, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{emailAddress = Name}); +make_config([{default_bits, Bits}|T], C) when is_integer(Bits) -> + make_config(T, C#config{default_bits = Bits}); +make_config([{v2_crls, Bool}|T], C) when is_boolean(Bool) -> + make_config(T, C#config{v2_crls = Bool}); +make_config([{crl_port, Port}|T], C) when is_integer(Port) -> + make_config(T, C#config{crl_port = Port}); +make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) -> + make_config(T, C#config{ecc_certs = Bool}); +make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) -> + make_config(T, C#config{issuing_distribution_point = Bool}); +make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) -> + make_config(T, C#config{openssl_cmd = Cmd}). + + +all([DataDir, PrivDir]) -> + all(DataDir, PrivDir). all(DataDir, PrivDir) -> - OpenSSLCmd = "openssl", + all(DataDir, PrivDir, #config{}). + +all(DataDir, PrivDir, C) when is_list(C) -> + all(DataDir, PrivDir, make_config(C)); +all(DataDir, PrivDir, C = #config{}) -> + ok = filelib:ensure_dir(filename:join(PrivDir, "erlangCA")), create_rnd(DataDir, PrivDir), % For all requests - rootCA(PrivDir, OpenSSLCmd, "erlangCA"), - intermediateCA(PrivDir, OpenSSLCmd, "otpCA", "erlangCA"), - endusers(PrivDir, OpenSSLCmd, "otpCA", ["client", "server"]), - collect_certs(PrivDir, ["erlangCA", "otpCA"], ["client", "server"]), - %% Create keycert files + rootCA(PrivDir, "erlangCA", C), + intermediateCA(PrivDir, "otpCA", "erlangCA", C), + endusers(PrivDir, "otpCA", ["client", "server", "revoked"], C), + endusers(PrivDir, "erlangCA", ["localhost"], C), + %% Create keycert files SDir = filename:join([PrivDir, "server"]), SC = filename:join([SDir, "cert.pem"]), SK = filename:join([SDir, "key.pem"]), @@ -46,7 +94,14 @@ all(DataDir, PrivDir) -> CK = filename:join([CDir, "key.pem"]), CKC = filename:join([CDir, "keycert.pem"]), append_files([CK, CC], CKC), - remove_rnd(PrivDir). + RDir = filename:join([PrivDir, "revoked"]), + RC = filename:join([RDir, "cert.pem"]), + RK = filename:join([RDir, "key.pem"]), + RKC = filename:join([RDir, "keycert.pem"]), + revoke(PrivDir, "otpCA", "revoked", C), + append_files([RK, RC], RKC), + remove_rnd(PrivDir), + {ok, C}. append_files(FileNames, ResultFileName) -> {ok, ResultFile} = file:open(ResultFileName, [write]), @@ -59,117 +114,182 @@ do_append_files([F|Fs], RF) -> ok = file:write(RF, Data), do_append_files(Fs, RF). -rootCA(Root, OpenSSLCmd, Name) -> - create_ca_dir(Root, Name, ca_cnf(Name)), - DN = #dn{commonName = Name}, - create_self_signed_cert(Root, OpenSSLCmd, Name, req_cnf(DN)), - ok. +rootCA(Root, Name, C) -> + create_ca_dir(Root, Name, ca_cnf(C#config{commonName = Name})), + create_self_signed_cert(Root, Name, req_cnf(C#config{commonName = Name}), C), + file:copy(filename:join([Root, Name, "cert.pem"]), filename:join([Root, Name, "cacerts.pem"])), + gencrl(Root, Name, C). -intermediateCA(Root, OpenSSLCmd, CA, ParentCA) -> - CA = "otpCA", - create_ca_dir(Root, CA, ca_cnf(CA)), +intermediateCA(Root, CA, ParentCA, C) -> + create_ca_dir(Root, CA, ca_cnf(C#config{commonName = CA})), CARoot = filename:join([Root, CA]), - DN = #dn{commonName = CA}, CnfFile = filename:join([CARoot, "req.cnf"]), - file:write_file(CnfFile, req_cnf(DN)), - KeyFile = filename:join([CARoot, "private", "key.pem"]), - ReqFile = filename:join([CARoot, "req.pem"]), - create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), + file:write_file(CnfFile, req_cnf(C#config{commonName = CA})), + KeyFile = filename:join([CARoot, "private", "key.pem"]), + ReqFile = filename:join([CARoot, "req.pem"]), + create_req(Root, CnfFile, KeyFile, ReqFile, C), CertFile = filename:join([CARoot, "cert.pem"]), - sign_req(Root, OpenSSLCmd, ParentCA, "ca_cert", ReqFile, CertFile). - -endusers(Root, OpenSSLCmd, CA, Users) -> - lists:foreach(fun(User) -> enduser(Root, OpenSSLCmd, CA, User) end, Users). - -enduser(Root, OpenSSLCmd, CA, User) -> + sign_req(Root, ParentCA, "ca_cert", ReqFile, CertFile, C), + CACertsFile = filename:join(CARoot, "cacerts.pem"), + file:copy(filename:join([Root, ParentCA, "cacerts.pem"]), CACertsFile), + %% append this CA's cert to the cacerts file + {ok, Bin} = file:read_file(CertFile), + {ok, FD} = file:open(CACertsFile, [append]), + file:write(FD, ["\n", Bin]), + file:close(FD), + gencrl(Root, CA, C). + +endusers(Root, CA, Users, C) -> + [enduser(Root, CA, User, C) || User <- Users]. + +enduser(Root, CA, User, C) -> UsrRoot = filename:join([Root, User]), file:make_dir(UsrRoot), CnfFile = filename:join([UsrRoot, "req.cnf"]), - DN = #dn{commonName = User}, - file:write_file(CnfFile, req_cnf(DN)), - KeyFile = filename:join([UsrRoot, "key.pem"]), - ReqFile = filename:join([UsrRoot, "req.pem"]), - create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), + file:write_file(CnfFile, req_cnf(C#config{commonName = User})), + KeyFile = filename:join([UsrRoot, "key.pem"]), + ReqFile = filename:join([UsrRoot, "req.pem"]), + create_req(Root, CnfFile, KeyFile, ReqFile, C), + %create_req(Root, CnfFile, KeyFile, ReqFile), CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]), - sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFileAllUsage), + sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C), CertFileDigitalSigOnly = filename:join([UsrRoot, "digital_signature_only_cert.pem"]), - sign_req(Root, OpenSSLCmd, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly). - -collect_certs(Root, CAs, Users) -> - Bins = lists:foldr( - fun(CA, Acc) -> - File = filename:join([Root, CA, "cert.pem"]), - {ok, Bin} = file:read_file(File), - [Bin, "\n" | Acc] - end, [], CAs), - lists:foreach( - fun(User) -> - File = filename:join([Root, User, "cacerts.pem"]), - file:write_file(File, Bins) - end, Users). + sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C), + CACertsFile = filename:join(UsrRoot, "cacerts.pem"), + file:copy(filename:join([Root, CA, "cacerts.pem"]), CACertsFile), + ok. -create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) -> +revoke(Root, CA, User, C) -> + UsrCert = filename:join([Root, User, "cert.pem"]), + CACnfFile = filename:join([Root, CA, "ca.cnf"]), + Cmd = [C#config.openssl_cmd, " ca" + " -revoke ", UsrCert, + [" -crl_reason keyCompromise" || C#config.v2_crls ], + " -config ", CACnfFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env), + gencrl(Root, CA, C). + +gencrl(Root, CA, C) -> + CACnfFile = filename:join([Root, CA, "ca.cnf"]), + CACRLFile = filename:join([Root, CA, "crl.pem"]), + Cmd = [C#config.openssl_cmd, " ca" + " -gencrl ", + " -crlhours 24", + " -out ", CACRLFile, + " -config ", CACnfFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + +verify(Root, CA, User, C) -> + CAFile = filename:join([Root, User, "cacerts.pem"]), + CACRLFile = filename:join([Root, CA, "crl.pem"]), + CertFile = filename:join([Root, User, "cert.pem"]), + Cmd = [C#config.openssl_cmd, " verify" + " -CAfile ", CAFile, + " -CRLfile ", CACRLFile, %% this is undocumented, but seems to work + " -crl_check ", + CertFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + try cmd(Cmd, Env) catch + exit:{eval_cmd, _, _} -> + invalid + end. + +create_self_signed_cert(Root, CAName, Cnf, C = #config{ecc_certs = true}) -> CARoot = filename:join([Root, CAName]), CnfFile = filename:join([CARoot, "req.cnf"]), file:write_file(CnfFile, Cnf), - KeyFile = filename:join([CARoot, "private", "key.pem"]), - CertFile = filename:join([CARoot, "cert.pem"]), - Cmd = [OpenSSLCmd, " req" + KeyFile = filename:join([CARoot, "private", "key.pem"]), + CertFile = filename:join([CARoot, "cert.pem"]), + Cmd = [C#config.openssl_cmd, " ecparam" + " -out ", KeyFile, + " -name secp521r1 ", + %" -name sect283k1 ", + " -genkey "], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env), + + Cmd2 = [C#config.openssl_cmd, " req" + " -new" + " -x509" + " -config ", CnfFile, + " -key ", KeyFile, + " -outform PEM ", + " -out ", CertFile], + cmd(Cmd2, Env); +create_self_signed_cert(Root, CAName, Cnf, C) -> + CARoot = filename:join([Root, CAName]), + CnfFile = filename:join([CARoot, "req.cnf"]), + file:write_file(CnfFile, Cnf), + KeyFile = filename:join([CARoot, "private", "key.pem"]), + CertFile = filename:join([CARoot, "cert.pem"]), + Cmd = [C#config.openssl_cmd, " req" " -new" " -x509" " -config ", CnfFile, " -keyout ", KeyFile, - " -out ", CertFile], - Env = [{"ROOTDIR", Root}], - cmd(Cmd, Env), - fix_key_file(OpenSSLCmd, KeyFile). - -% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format -fix_key_file(OpenSSLCmd, KeyFile) -> - KeyFileTmp = KeyFile ++ ".tmp", - Cmd = [OpenSSLCmd, " rsa", - " -in ", - KeyFile, - " -out ", - KeyFileTmp], - cmd(Cmd, []), - ok = file:rename(KeyFileTmp, KeyFile). + " -outform PEM", + " -out ", CertFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + create_ca_dir(Root, CAName, Cnf) -> CARoot = filename:join([Root, CAName]), + ok = filelib:ensure_dir(CARoot), file:make_dir(CARoot), create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]), create_rnd(Root, filename:join([CAName, "private"])), create_files(CARoot, [{"serial", "01\n"}, + {"crlnumber", "01"}, {"index.txt", ""}, {"ca.cnf", Cnf}]). -create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) -> - Cmd = [OpenSSLCmd, " req" +create_req(Root, CnfFile, KeyFile, ReqFile, C = #config{ecc_certs = true}) -> + Cmd = [C#config.openssl_cmd, " ecparam" + " -out ", KeyFile, + " -name secp521r1 ", + %" -name sect283k1 ", + " -genkey "], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env), + Cmd2 = [C#config.openssl_cmd, " req" + " -new ", + " -key ", KeyFile, + " -outform PEM ", + " -out ", ReqFile, + " -config ", CnfFile], + cmd(Cmd2, Env); + %fix_key_file(KeyFile). +create_req(Root, CnfFile, KeyFile, ReqFile, C) -> + Cmd = [C#config.openssl_cmd, " req" " -new" " -config ", CnfFile, - " -keyout ", KeyFile, - " -out ", ReqFile], - Env = [{"ROOTDIR", Root}], - cmd(Cmd, Env), - fix_key_file(OpenSSLCmd, KeyFile). + " -outform PEM ", + " -keyout ", KeyFile, + " -out ", ReqFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + %fix_key_file(KeyFile). + -sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) -> +sign_req(Root, CA, CertType, ReqFile, CertFile, C) -> CACnfFile = filename:join([Root, CA, "ca.cnf"]), - Cmd = [OpenSSLCmd, " ca" + Cmd = [C#config.openssl_cmd, " ca" " -batch" " -notext" - " -config ", CACnfFile, + " -config ", CACnfFile, " -extensions ", CertType, - " -in ", ReqFile, + " -in ", ReqFile, " -out ", CertFile], - Env = [{"ROOTDIR", Root}], + Env = [{"ROOTDIR", filename:absname(Root)}], cmd(Cmd, Env). - + %% %% Misc %% - + create_dirs(Root, Dirs) -> lists:foreach(fun(Dir) -> file:make_dir(filename:join([Root, Dir])) end, @@ -192,30 +312,30 @@ remove_rnd(Dir) -> cmd(Cmd, Env) -> FCmd = lists:flatten(Cmd), - Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout, + Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout, {env, Env}]), - eval_cmd(Port). + eval_cmd(Port, FCmd). -eval_cmd(Port) -> - receive +eval_cmd(Port, Cmd) -> + receive {Port, {data, _}} -> - eval_cmd(Port); + eval_cmd(Port, Cmd); {Port, eof} -> ok end, receive {Port, {exit_status, Status}} when Status /= 0 -> %% io:fwrite("exit status: ~w~n", [Status]), - exit({eval_cmd, Status}) + exit({eval_cmd, Cmd, Status}) after 0 -> ok end. %% -%% Contents of configuration files +%% Contents of configuration files %% -req_cnf(DN) -> +req_cnf(C) -> ["# Purpose: Configuration for requests (end users and CAs)." "\n" "ROOTDIR = $ENV::ROOTDIR\n" @@ -224,10 +344,10 @@ req_cnf(DN) -> "[req]\n" "input_password = secret\n" "output_password = secret\n" - "default_bits = 1024\n" + "default_bits = ", integer_to_list(C#config.default_bits), "\n" "RANDFILE = $ROOTDIR/RAND\n" "encrypt_key = no\n" - "default_md = sha1\n" + "default_md = md5\n" "#string_mask = pkix\n" "x509_extensions = ca_ext\n" "prompt = no\n" @@ -235,12 +355,12 @@ req_cnf(DN) -> "\n" "[name]\n" - "commonName = ", DN#dn.commonName, "\n" - "organizationalUnitName = ", DN#dn.organizationalUnitName, "\n" - "organizationName = ", DN#dn.organizationName, "\n" - "localityName = ", DN#dn.localityName, "\n" - "countryName = ", DN#dn.countryName, "\n" - "emailAddress = ", DN#dn.emailAddress, "\n" + "commonName = ", C#config.commonName, "\n" + "organizationalUnitName = ", C#config.organizationalUnitName, "\n" + "organizationName = ", C#config.organizationName, "\n" + "localityName = ", C#config.localityName, "\n" + "countryName = ", C#config.countryName, "\n" + "emailAddress = ", C#config.emailAddress, "\n" "\n" "[ca_ext]\n" @@ -249,8 +369,7 @@ req_cnf(DN) -> "subjectKeyIdentifier = hash\n" "subjectAltName = email:copy\n"]. - -ca_cnf(CA) -> +ca_cnf(C) -> ["# Purpose: Configuration for CAs.\n" "\n" "ROOTDIR = $ENV::ROOTDIR\n" @@ -258,21 +377,23 @@ ca_cnf(CA) -> "\n" "[ca]\n" - "dir = $ROOTDIR/", CA, "\n" + "dir = $ROOTDIR/", C#config.commonName, "\n" "certs = $dir/certs\n" "crl_dir = $dir/crl\n" "database = $dir/index.txt\n" "new_certs_dir = $dir/newcerts\n" "certificate = $dir/cert.pem\n" "serial = $dir/serial\n" - "crl = $dir/crl.pem\n" + "crl = $dir/crl.pem\n", + ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls], "private_key = $dir/private/key.pem\n" "RANDFILE = $dir/private/RAND\n" "\n" - "x509_extensions = user_cert\n" + "x509_extensions = user_cert\n", + ["crl_extensions = crl_ext\n" || C#config.v2_crls], "unique_subject = no\n" "default_days = 3600\n" - "default_md = sha1\n" + "default_md = md5\n" "preserve = no\n" "policy = policy_match\n" "\n" @@ -286,6 +407,13 @@ ca_cnf(CA) -> "emailAddress = supplied\n" "\n" + "[crl_ext]\n" + "authorityKeyIdentifier=keyid:always,issuer:always\n", + ["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point], + + "[idpsec]\n" + "fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n" + "[user_cert]\n" "basicConstraints = CA:false\n" "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" @@ -293,6 +421,12 @@ ca_cnf(CA) -> "authorityKeyIdentifier = keyid,issuer:always\n" "subjectAltName = email:copy\n" "issuerAltName = issuer:copy\n" + "crlDistributionPoints=@crl_section\n" + + "[crl_section]\n" + %% intentionally invalid + "URI.1=http://localhost/",C#config.commonName,"/crl.pem\n" + "URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n" "\n" "[user_cert_digital_signature_only]\n" @@ -310,4 +444,7 @@ ca_cnf(CA) -> "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid:always,issuer:always\n" "subjectAltName = email:copy\n" - "issuerAltName = issuer:copy\n"]. + "issuerAltName = issuer:copy\n" + "crlDistributionPoints=@crl_section\n" + ]. + |