%% The purpose of this module is to create example certificates for
%% testing.
%% Run it as:
%%
%% erl -noinput -run make_certs all "/path/to/openssl" -s erlang halt
%%
-module(make_certs).
-export([all/0, all/1]).
-record(dn, {commonName,
organizationalUnitName = "Erlang OTP",
organizationName = "Ericsson AB",
localityName = "Stockholm",
countryName = "SE",
emailAddress = "[email protected]"}).
all() ->
all(["openssl"]).
all([OpenSSLCmd]) ->
Root = filename:dirname(filename:dirname((code:which(?MODULE)))),
%% io:fwrite("Root : ~s~n", [Root]),
NRoot = filename:join([Root, "etc"]),
file:make_dir(NRoot),
create_rnd(Root, "etc"), % For all requests
rootCA(NRoot, OpenSSLCmd, "erlangCA"),
intermediateCA(NRoot, OpenSSLCmd, "otpCA", "erlangCA"),
endusers(NRoot, OpenSSLCmd, "otpCA", ["client", "server"]),
collect_certs(NRoot, ["erlangCA", "otpCA"], ["client", "server"]),
remove_rnd(Root, "etc").
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),
CertFile = filename:join([UsrRoot, "cert.pem"]),
sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFile).
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).
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).
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(Root, Dir) ->
From = filename:join([Root, "rnd", "RAND"]),
To = filename:join([Root, Dir, "RAND"]),
file:copy(From, To).
remove_rnd(Root, Dir) ->
File = filename:join([Root, Dir, "RAND"]),
file:delete(File).
cmd(Cmd, Env) ->
FCmd = lists:flatten(Cmd),
Port = open_port({spawn, FCmd}, [stream, eof, exit_status,
{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]),
erlang:halt(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"
"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"
"[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"].