4 Creating Certificates
Here we consider the creation of example certificates.
4.1 The openssl Command
The
openssl
command is a utility that comes with the OpenSSL distribution. It provides a variety of subcommands. Each subcommand is invoked asopenssl subcmd <options and arguments>where
subcmd
denotes the subcommand in question.We shall use the following subcommands to create certificates for the purpose of testing Erlang/OTP SSL:
- req to create certificate requests and a self-signed certificates,
- ca to create certificates from certificate requests.
We create the following certificates:
- the erlangCA root certificate (a self-signed certificate),
- the otpCA certificate signed by the erlangCA,
- a client certificate signed by the otpCA, and
- a server certificate signed by the otpCA.
4.1.1 The openssl configuration file
An
openssl
configuration file consist of a number of sections, where each section starts with one line containing[ section_name ]
, wheresection_name
is the name of the section. The first section of the file is either unnamed, or is named[ default ]
. For further details see the OpenSSL config(5) manual page.The required sections for the subcommands we are going to use are as follows:
subcommand required/default section override command line option configuration file option req [req] - -config FILE
ca [ca] -name section
-config FILE
openssl subcommands to use 4.1.2 Creating the Erlang root CA
The Erlang root CA is created with the command
openssl req -new -x509 -config /some/path/req.cnf \ -keyout /some/path/key.pem -out /some/path/cert.pemwhere the option
-new
indicates that we want to create a new certificate request and the option-x509
implies that a self-signed certificate is created.4.1.3 Creating the OTP CA
The OTP CA is created by first creating a certificate request with the command
openssl req -new -config /some/path/req.cnf \ -keyout /some/path/key.pem -out /some/path/req.pemand the ask the Erlang CA to sign it:
openssl ca -batch -notext -config /some/path/req.cnf \ -extensions ca_cert -in /some/path/req.pem -out /some/path/cert.pemwhere the option
-extensions
refers to a section in the configuration file saying that it should create a CA certificate, and not a plain user certificate.The
client
andserver
certificates are created similarly, except that the option-extensions
then has the valueuser_cert
.4.2 An Example
The following module
create_certs
is used by the Erlang/OTP SSL application for generating certificates to be used in tests. The source code is also found inssl-X.Y.Z/examples/certs/src
.The purpose of the
create_certs:all/1
function is to make it possible to provide from theerl
command line, the full path name of theopenssl
command.Note that the module creates temporary OpenSSL configuration files for the
req
andca
subcommands.%% 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 = "peter@erix.ericsson.se"}). 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"].