> ## 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 as

>           openssl 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 ]`, where `section_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.pem

> where 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.pem

> and 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.pem

> where 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` and `server` certificates are created similarly, except that the option `-extensions` then has the value `user_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 in `ssl-X.Y.Z/examples/certs/src`.

> The purpose of the `create_certs:all/1` function is to make it possible to provide from the `erl` command line, the full path name of the `openssl` command.

> Note that the module creates temporary OpenSSL configuration files for the `req` and `ca` 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"].

Copyright © 1991-2003 Ericsson Utvecklings AB