aboutsummaryrefslogtreecommitdiffstats
path: root/lib/eldap/test
diff options
context:
space:
mode:
authorHans Nilsson <[email protected]>2013-10-15 20:56:37 +0200
committerHans Nilsson <[email protected]>2013-10-28 16:48:57 +0100
commite2b0dfac40f2f7f0aa0d74ca902ea5f867c06cd1 (patch)
treea8a93a1d20c981cd6a7196b496a335c35a9ff4ce /lib/eldap/test
parent207a13a549052e014a82362032995e347ffb68ff (diff)
downloadotp-e2b0dfac40f2f7f0aa0d74ca902ea5f867c06cd1.tar.gz
otp-e2b0dfac40f2f7f0aa0d74ca902ea5f867c06cd1.tar.bz2
otp-e2b0dfac40f2f7f0aa0d74ca902ea5f867c06cd1.zip
eldap: Add START_TLS (OTP-11336)
Diffstat (limited to 'lib/eldap/test')
-rw-r--r--lib/eldap/test/README36
-rw-r--r--lib/eldap/test/eldap.cfg1
-rw-r--r--lib/eldap/test/eldap_basic_SUITE.erl174
-rw-r--r--lib/eldap/test/eldap_basic_SUITE_data/certs/README1
-rw-r--r--lib/eldap/test/ldap_server/slapd.conf30
-rw-r--r--lib/eldap/test/make_certs.erl313
6 files changed, 504 insertions, 51 deletions
diff --git a/lib/eldap/test/README b/lib/eldap/test/README
new file mode 100644
index 0000000000..449cdfc0d3
--- /dev/null
+++ b/lib/eldap/test/README
@@ -0,0 +1,36 @@
+
+This works for me on Ubuntu.
+
+To run thoose test you need
+ 1) some certificates
+ 2) a running ldap server, for example OpenLDAPs slapd. See http://www.openldap.org/doc/admin24
+
+1)-------
+To generate certificates:
+erl
+> make_certs:all("/dev/null", "eldap_basic_SUITE_data/certs").
+
+2)-------
+To start slapd:
+ sudo slapd -f $ERL_TOP/lib/eldap/test/ldap_server/myslapd.conf -F /tmp/slapd/slapd.d -h "ldap://localhost:9876 ldaps://localhost:9877"
+
+This will however not work, since slapd is guarded by apparmor that checks that slapd does not access other than allowed files...
+
+To make a local extension of alowed operations:
+ sudo emacs /etc/apparmor.d/local/usr.sbin.slapd
+
+and, after the change (yes, at least on Ubuntu it is right to edit ../local/.. but run with an other file) :
+
+ sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.slapd
+
+
+The local file looks like this for me:
+
+# Site-specific additions and overrides for usr.sbin.slapd.
+# For more details, please see /etc/apparmor.d/local/README.
+
+/etc/pkcs11/** r,
+/usr/lib/x86_64-linux-gnu/** rm,
+
+/ldisk/hans_otp/otp/lib/eldap/test/** rw,
+/tmp/slapd/** rwk,
diff --git a/lib/eldap/test/eldap.cfg b/lib/eldap/test/eldap.cfg
new file mode 100644
index 0000000000..3a24afa067
--- /dev/null
+++ b/lib/eldap/test/eldap.cfg
@@ -0,0 +1 @@
+{eldap_server,{"localhost",389}}.
diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl
index c7e3052b29..127d753b92 100644
--- a/lib/eldap/test/eldap_basic_SUITE.erl
+++ b/lib/eldap/test/eldap_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2013. 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
@@ -27,39 +27,36 @@
-define(TIMEOUT, 120000). % 2 min
-init_per_suite(Config0) ->
- {{EldapHost,Port}, Config1} =
- case catch ct:get_config(eldap_server, undefined) of
- undefined -> %% Dev test only
- Server = {"localhost", 9876},
- {Server, [{eldap_server, {"localhost", 9876}}|Config0]};
- {'EXIT', _} -> %% Dev test only
- Server = {"localhost", 9876},
- {Server, [{eldap_server, {"localhost", 9876}}|Config0]};
- Server ->
- {Server, [{eldap_server, Server}|Config0]}
- end,
- %% Add path for this test run
+init_per_suite(Config) ->
+ ssl:start(),
+ chk_config(ldap_server, {"localhost",9876},
+ chk_config(ldaps_server, {"localhost",9877},
+ Config)).
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config0) ->
+ {EldapHost,Port} = proplists:get_value(ldap_server,Config0),
try
- {ok, Handle} = eldap:open([EldapHost], [{port, Port}]),
+ {ok, Handle} = eldap:open([EldapHost], [{port,Port}]),
ok = eldap:simple_bind(Handle, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
{ok, MyHost} = inet:gethostname(),
Path = "dc="++MyHost++",dc=ericsson,dc=se",
- Config = [{eldap_path,Path}|Config1],
eldap:add(Handle,"dc=ericsson,dc=se",
[{"objectclass", ["dcObject", "organization"]},
{"dc", ["ericsson"]}, {"o", ["Testing"]}]),
eldap:add(Handle,Path,
[{"objectclass", ["dcObject", "organization"]},
{"dc", [MyHost]}, {"o", ["Test machine"]}]),
- Config
+ [{eldap_path,Path}|Config0]
catch error:{badmatch,Error} ->
io:format("Eldap init error ~p~n ~p~n",[Error, erlang:get_stacktrace()]),
- {skip, lists:flatten(io_lib:format("Ldap init failed with host ~p", [EldapHost]))}
+ {skip, lists:flatten(io_lib:format("Ldap init failed with host ~p:~p. Error=~p", [EldapHost,Port,Error]))}
end.
-end_per_suite(Config) ->
- %% Cleanup everything
- {EHost, Port} = proplists:get_value(eldap_server, Config),
+
+end_per_testcase(_TestCase, Config) ->
+ {EHost, Port} = proplists:get_value(ldap_server, Config),
Path = proplists:get_value(eldap_path, Config),
{ok, H} = eldap:open([EHost], [{port, Port}]),
ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
@@ -71,16 +68,20 @@ end_per_suite(Config) ->
[ok = eldap:delete(H, Entry) || {eldap_entry, Entry, _} <- Entries];
_ -> ignore
end,
- ok.
-init_per_testcase(_TestCase, Config) -> Config.
-end_per_testcase(_TestCase, _Config) -> ok.
+ ok.
%% suite() ->
all() ->
[app,
- api].
+ api,
+ ssl_api,
+ start_tls,
+ tls_operations,
+ start_tls_twice,
+ start_tls_on_ssl
+ ].
app(doc) -> "Test that the eldap app file is ok";
app(suite) -> [];
@@ -90,21 +91,89 @@ app(Config) when is_list(Config) ->
api(doc) -> "Basic test that all api functions works as expected";
api(suite) -> [];
api(Config) ->
- {Host,Port} = proplists:get_value(eldap_server, Config),
+ {Host,Port} = proplists:get_value(ldap_server, Config),
{ok, H} = eldap:open([Host], [{port,Port}]),
%% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]),
+ do_api_checks(H, Config),
+ eldap:close(H),
+ ok.
+
+
+ssl_api(doc) -> "Basic test that all api functions works as expected";
+ssl_api(suite) -> [];
+ssl_api(Config) ->
+ {Host,Port} = proplists:get_value(ldaps_server, Config),
+ {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
+ do_api_checks(H, Config),
+ eldap:close(H),
+ ok.
+
+
+start_tls(doc) -> "Test that an existing (tcp) connection can be upgraded to tls";
+start_tls(suite) -> [];
+start_tls(Config) ->
+ {Host,Port} = proplists:get_value(ldap_server, Config),
+ {ok, H} = eldap:open([Host], [{port,Port}]),
+ ok = eldap:start_tls(H, [
+ {keyfile, filename:join([proplists:get_value(data_dir,Config),
+ "certs/client/key.pem"])}
+ ]),
+ eldap:close(H).
+
+
+tls_operations(doc) -> "Test that an upgraded connection is usable for ldap stuff";
+tls_operations(suite) -> [];
+tls_operations(Config) ->
+ {Host,Port} = proplists:get_value(ldap_server, Config),
+ {ok, H} = eldap:open([Host], [{port,Port}]),
+ ok = eldap:start_tls(H, [
+ {keyfile, filename:join([proplists:get_value(data_dir,Config),
+ "certs/client/key.pem"])}
+ ]),
+ do_api_checks(H, Config),
+ eldap:close(H).
+
+start_tls_twice(doc) -> "Test that start_tls on an already upgraded connection fails";
+start_tls_twice(suite) -> [];
+start_tls_twice(Config) ->
+ {Host,Port} = proplists:get_value(ldap_server, Config),
+ {ok, H} = eldap:open([Host], [{port,Port}]),
+ ok = eldap:start_tls(H, []),
+ {error,tls_already_started} = eldap:start_tls(H, []),
+ do_api_checks(H, Config),
+ eldap:close(H).
+
+
+start_tls_on_ssl(doc) -> "Test that start_tls on an ldaps connection fails";
+start_tls_on_ssl(suite) -> [];
+start_tls_on_ssl(Config) ->
+ {Host,Port} = proplists:get_value(ldaps_server, Config),
+ {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
+ {error,tls_already_started} = eldap:start_tls(H, []),
+ do_api_checks(H, Config),
+ eldap:close(H).
+
+
+%%%--------------------------------------------------------------------------------
+chk_config(Key, Default, Config) ->
+ case catch ct:get_config(ldap_server, undefined) of
+ undefined -> [{Key,Default} | Config ];
+ {'EXIT',_} -> [{Key,Default} | Config ];
+ Value -> [{Key,Value} | Config]
+ end.
+
+
+
+do_api_checks(H, Config) ->
BasePath = proplists:get_value(eldap_path, Config),
+
All = fun(Where) ->
eldap:search(H, #eldap_search{base=Where,
filter=eldap:present("objectclass"),
scope= eldap:wholeSubtree()})
end,
- Search = fun(Filter) ->
- eldap:search(H, #eldap_search{base=BasePath,
- filter=Filter,
- scope=eldap:singleLevel()})
- end,
- {ok, #eldap_search_result{entries=[_]}} = All(BasePath),
+ {ok, #eldap_search_result{entries=[_XYZ]}} = All(BasePath),
+%% ct:log("XYZ=~p",[_XYZ]),
{error, noSuchObject} = All("cn=Bar,"++BasePath),
{error, _} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
@@ -112,52 +181,67 @@ api(Config) ->
{"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- %% Add
+ chk_add(H, BasePath),
+ {ok,FB} = chk_search(H, BasePath),
+ chk_modify(H, FB),
+ chk_delete(H, BasePath),
+ chk_modify_dn(H, FB).
+
+
+chk_add(H, BasePath) ->
ok = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
[{"objectclass", ["person"]},
{"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
+ {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
ok = eldap:add(H, "cn=Foo Bar," ++ BasePath,
[{"objectclass", ["person"]},
{"cn", ["Foo Bar"]}, {"sn", ["Bar"]}, {"telephoneNumber", ["555-1232", "555-5432"]}]),
ok = eldap:add(H, "ou=Team," ++ BasePath,
[{"objectclass", ["organizationalUnit"]},
- {"ou", ["Team"]}]),
+ {"ou", ["Team"]}]).
- %% Search
+chk_search(H, BasePath) ->
+ Search = fun(Filter) ->
+ eldap:search(H, #eldap_search{base=BasePath,
+ filter=Filter,
+ scope=eldap:singleLevel()})
+ end,
JJSR = {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:equalityMatch("sn", "Jonsson")),
JJSR = Search(eldap:substrings("sn", [{any, "ss"}])),
FBSR = {ok, #eldap_search_result{entries=[#eldap_entry{object_name=FB}]}} =
Search(eldap:substrings("sn", [{any, "a"}])),
FBSR = Search(eldap:substrings("sn", [{initial, "B"}])),
FBSR = Search(eldap:substrings("sn", [{final, "r"}])),
-
F_AND = eldap:'and'([eldap:present("objectclass"), eldap:present("ou")]),
{ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(F_AND),
F_NOT = eldap:'and'([eldap:present("objectclass"), eldap:'not'(eldap:present("ou"))]),
{ok, #eldap_search_result{entries=[#eldap_entry{}, #eldap_entry{}]}} = Search(F_NOT),
+ {ok,FB}. %% FIXME
- %% MODIFY
+chk_modify(H, FB) ->
Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]),
eldap:mod_add("description", ["Nice guy"])],
%% io:format("MOD ~p ~p ~n",[FB, Mod]),
ok = eldap:modify(H, FB, Mod),
%% DELETE ATTR
- ok = eldap:modify(H, FB, [eldap:mod_delete("telephoneNumber", [])]),
+ ok = eldap:modify(H, FB, [eldap:mod_delete("telephoneNumber", [])]).
- %% DELETE
+
+chk_delete(H, BasePath) ->
{error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
[{"objectclass", ["person"]},
{"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
ok = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath),
- {error, noSuchObject} = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath),
+ {error, noSuchObject} = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath).
- %% MODIFY_DN
- ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, ""),
- %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]),
+chk_modify_dn(H, FB) ->
+ ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, "").
+ %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]).
- eldap:close(H),
- ok.
+%%%----------------
add(H, Attr, Value, Path0, Attrs, Class) ->
Path = case Path0 of
[] -> Attr ++ "=" ++ Value;
diff --git a/lib/eldap/test/eldap_basic_SUITE_data/certs/README b/lib/eldap/test/eldap_basic_SUITE_data/certs/README
new file mode 100644
index 0000000000..a7c8e9dc2e
--- /dev/null
+++ b/lib/eldap/test/eldap_basic_SUITE_data/certs/README
@@ -0,0 +1 @@
+See ../../README
diff --git a/lib/eldap/test/ldap_server/slapd.conf b/lib/eldap/test/ldap_server/slapd.conf
index 87be676d9f..eca298c866 100644
--- a/lib/eldap/test/ldap_server/slapd.conf
+++ b/lib/eldap/test/ldap_server/slapd.conf
@@ -1,14 +1,32 @@
-include /etc/ldap/schema/core.schema
-pidfile /tmp/openldap-data/slapd.pid
-argsfile /tmp/openldap-data/slapd.args
+modulepath /usr/lib/ldap
+moduleload back_bdb.la
+
+# example config file - global configuration section
+include /etc/ldap/schema/core.schema
+referral ldap://root.openldap.org
+access to * by * read
+
+TLSCACertificateFile /ldisk/hans_otp/otp/lib/eldap/test/eldap_basic_SUITE_data/certs/server/cacerts.pem
+TLSCertificateFile /ldisk/hans_otp/otp/lib/eldap/test/eldap_basic_SUITE_data/certs/server/cert.pem
+TLSCertificateKeyFile /ldisk/hans_otp/otp/lib/eldap/test/eldap_basic_SUITE_data/certs/server/keycert.pem
+
database bdb
suffix "dc=ericsson,dc=se"
rootdn "cn=Manager,dc=ericsson,dc=se"
rootpw hejsan
+
# The database must exist before running slapd
-directory /tmp/openldap-data
+directory /tmp/slapd/openldap-data-ericsson.se
+
# Indices to maintain
index objectClass eq
-# URI "ldap://0.0.0.0:9876 ldaps://0.0.0.0:9870"
-# servers/slapd/slapd -d 255 -h "ldap://0.0.0.0:9876 ldaps://0.0.0.0:9870" -f /ldisk/dgud/src/otp/lib/eldap/test/ldap_server/slapd.conf \ No newline at end of file
+access to attrs=userPassword
+ by self write
+ by anonymous auth
+ by dn.base="cn=Manager,dc=ericsson,dc=se" write
+ by * none
+access to *
+ by self write
+ by dn.base="cn=Manager,dc=ericsson,dc=se" write
+ by * read
diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl
new file mode 100644
index 0000000000..f963af180d
--- /dev/null
+++ b/lib/eldap/test/make_certs.erl
@@ -0,0 +1,313 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2013. 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).
+
+-export([all/2]).
+
+-record(dn, {commonName,
+ organizationalUnitName = "Erlang OTP",
+ organizationName = "Ericsson AB",
+ localityName = "Stockholm",
+ countryName = "SE",
+ emailAddress = "[email protected]"}).
+
+all(DataDir, PrivDir) ->
+ OpenSSLCmd = "openssl",
+ 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
+ SDir = filename:join([PrivDir, "server"]),
+ SC = filename:join([SDir, "cert.pem"]),
+ SK = filename:join([SDir, "key.pem"]),
+ SKC = filename:join([SDir, "keycert.pem"]),
+ append_files([SK, SC], SKC),
+ CDir = filename:join([PrivDir, "client"]),
+ CC = filename:join([CDir, "cert.pem"]),
+ CK = filename:join([CDir, "key.pem"]),
+ CKC = filename:join([CDir, "keycert.pem"]),
+ append_files([CK, CC], CKC),
+ remove_rnd(PrivDir).
+
+append_files(FileNames, ResultFileName) ->
+ {ok, ResultFile} = file:open(ResultFileName, [write]),
+ do_append_files(FileNames, ResultFile).
+
+do_append_files([], RF) ->
+ ok = file:close(RF);
+do_append_files([F|Fs], RF) ->
+ {ok, Data} = file:read_file(F),
+ 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.
+
+intermediateCA(Root, OpenSSLCmd, CA, ParentCA) ->
+ CA = "otpCA",
+ create_ca_dir(Root, CA, ca_cnf(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),
+ 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) ->
+ 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),
+ CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]),
+ sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFileAllUsage),
+ 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).
+
+create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) ->
+ 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"
+ " -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).
+
+create_ca_dir(Root, CAName, Cnf) ->
+ CARoot = filename:join([Root, CAName]),
+ file:make_dir(CARoot),
+ create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]),
+ create_rnd(Root, filename:join([CAName, "private"])),
+ create_files(CARoot, [{"serial", "01\n"},
+ {"index.txt", ""},
+ {"ca.cnf", Cnf}]).
+
+create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) ->
+ Cmd = [OpenSSLCmd, " req"
+ " -new"
+ " -config ", CnfFile,
+ " -keyout ", KeyFile,
+ " -out ", ReqFile],
+ Env = [{"ROOTDIR", Root}],
+ cmd(Cmd, Env),
+ fix_key_file(OpenSSLCmd, KeyFile).
+
+sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ Cmd = [OpenSSLCmd, " ca"
+ " -batch"
+ " -notext"
+ " -config ", CACnfFile,
+ " -extensions ", CertType,
+ " -in ", ReqFile,
+ " -out ", CertFile],
+ Env = [{"ROOTDIR", Root}],
+ cmd(Cmd, Env).
+
+%%
+%% Misc
+%%
+
+create_dirs(Root, Dirs) ->
+ lists:foreach(fun(Dir) ->
+ file:make_dir(filename:join([Root, Dir])) end,
+ Dirs).
+
+create_files(Root, NameContents) ->
+ lists:foreach(
+ fun({Name, Contents}) ->
+ file:write_file(filename:join([Root, Name]), Contents) end,
+ NameContents).
+
+create_rnd(FromDir, ToDir) ->
+ From = filename:join([FromDir, "RAND"]),
+ To = filename:join([ToDir, "RAND"]),
+ file:copy(From, To).
+
+remove_rnd(Dir) ->
+ File = filename:join([Dir, "RAND"]),
+ file:delete(File).
+
+cmd(Cmd, Env) ->
+ FCmd = lists:flatten(Cmd),
+ Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,
+ {env, Env}]),
+ eval_cmd(Port).
+
+eval_cmd(Port) ->
+ receive
+ {Port, {data, _}} ->
+ eval_cmd(Port);
+ {Port, eof} ->
+ ok
+ end,
+ receive
+ {Port, {exit_status, Status}} when Status /= 0 ->
+ %% io:fwrite("exit status: ~w~n", [Status]),
+ exit({eval_cmd, Status})
+ after 0 ->
+ ok
+ end.
+
+%%
+%% Contents of configuration files
+%%
+
+req_cnf(DN) ->
+ ["# Purpose: Configuration for requests (end users and CAs)."
+ "\n"
+ "ROOTDIR = $ENV::ROOTDIR\n"
+ "\n"
+
+ "[req]\n"
+ "input_password = secret\n"
+ "output_password = secret\n"
+ "default_bits = 1024\n"
+ "RANDFILE = $ROOTDIR/RAND\n"
+ "encrypt_key = no\n"
+ "default_md = sha1\n"
+ "#string_mask = pkix\n"
+ "x509_extensions = ca_ext\n"
+ "prompt = no\n"
+ "distinguished_name= name\n"
+ "\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"
+ "\n"
+
+ "[ca_ext]\n"
+ "basicConstraints = critical, CA:true\n"
+ "keyUsage = cRLSign, keyCertSign\n"
+ "subjectKeyIdentifier = hash\n"
+ "subjectAltName = email:copy\n"].
+
+
+ca_cnf(CA) ->
+ ["# Purpose: Configuration for CAs.\n"
+ "\n"
+ "ROOTDIR = $ENV::ROOTDIR\n"
+ "default_ca = ca\n"
+ "\n"
+
+ "[ca]\n"
+ "dir = $ROOTDIR/", CA, "\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"
+ "private_key = $dir/private/key.pem\n"
+ "RANDFILE = $dir/private/RAND\n"
+ "\n"
+ "x509_extensions = user_cert\n"
+ "unique_subject = no\n"
+ "default_days = 3600\n"
+ "default_md = sha1\n"
+ "preserve = no\n"
+ "policy = policy_match\n"
+ "\n"
+
+ "[policy_match]\n"
+ "commonName = supplied\n"
+ "organizationalUnitName = optional\n"
+ "organizationName = match\n"
+ "countryName = match\n"
+ "localityName = match\n"
+ "emailAddress = supplied\n"
+ "\n"
+
+ "[user_cert]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ "\n"
+
+ "[user_cert_digital_signature_only]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = digitalSignature\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ "\n"
+
+ "[ca_cert]\n"
+ "basicConstraints = critical,CA:true\n"
+ "keyUsage = cRLSign, keyCertSign\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid:always,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"].