Example of certificate creation with OpenSSL

If you need to create certificates for a test setup, the online examples and O’Reilly OpenSSL book examples - while instructive - seem rather awkward and obscure. The fault lies with OpenSSL (otherwise a great piece of software) and the mash of parameters between the configuration file, the command line, and interactive entry. The mash makes OpenSSL usage obscure, so you get a lot of examples of cargo-cult programming in the public examples.

I hate cargo-cult programming.

When a command requires a complex set of parameters, I prefer to pull the parameters from a file. Parameter files can be generated by script, archived, and/or stored in source control. With OpenSSL you cannot (quite) do that. Even worse, the default configuration file mashes together parameters from distinct commands in a non-obvious way. Thus folk looking at OpenSSL usage are often uncertain about what is used and when … and you get “I hacked this until it worked, but not sure how” examples.

Since I have to feed parameters to OpenSSL both in the configuration file and on the command line, I chose to use shell scripts, create the configuration files from script when needed, and run the command with any remaining needed parameters.

The example is a Mercurial repository at:

http://hg.bannister.us/public/ca

Or just clone the set via:

hg clone hg.bannister.us:/public/ca

All the parameters for CA creation are in do/ca-create.sh

test -d root || mkdir root || exit 1
test -d history || mkdir history || exit 2

export password1='really cool password'
export password2="$password1"

export config=history/ca-create.cnf

cat > $config << XXX

default_md      = sha1
default_bits        = 2048
x509_extensions         = root_ca_certificate_extensions

# You will want to change this, when security matters.
input_password      = $password1
output_password     = $password2

[ root_ca_certificate_extensions ]

subjectKeyIdentifier    = hash
keyUsage        = digitalSignature,keyCertSign,cRLSign
#authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints    = critical,CA:true

[ req ]

prompt                  = no
distinguished_name  = root_ca_distinguished_name

[ root_ca_distinguished_name ]

# You will want to change this, to match your target server.
# The order of these attributes is significant.
countryName     = US
#stateOrProvinceName    = California
organizationName    = Bannister @ Home
organizationalUnitName  = Root Certification Authority
commonName      = bannister.home CA
emailAddress        = preston@bannister.us

XXX

openssl req -config $config -x509 -new -days 365 -keyout root/ca-private-key.pem -out root/ca-certificate.pem

The bits above that you will want to change are in italics. To create your CA run:

sh do/ca-create.sh

To get a certificate for a named entity, first you have to generate a certificate request. For public web servers, the certificate request is what you send to one of the CA roots (Verisign, etc.) along with money, to get your certificate signed. For test setups, better to sign your own certificates. (Large organizations - like the DoD - also sign their own certificates.) All the parameters for creating certificate requests, with the exception of the entity name, are in the file do/request.sh:

test -z "$1" && {
    echo 'Usage: request-certificate name'
    exit 1
}
test -d history || mkdir history || exit 2

export name=$1
echo name = $name

export password1='really cool password'
export password2="$password1"

export config=history/request-$name.cnf
export base="cn-$name"

test -d requests || mkdir requests || exit 1
test -d $base || mkdir $base || exit 2

cat > $config << XXX

default_md      = sha1
default_bits        = 2048
x509_extensions     = root_ca_issued_certificate_extensions

# You will want to change this, when security matters.
input_password      = $password1
output_password     = $password2

[ root_ca_issued_certificate_extensions ]

basicConstraints    = CA:false

# PKIX recommendations.
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid,issuer:always

# Typical in keyUsage for a client certificate.
# keyUsage      = nonRepudiation, digitalSignature, keyEncipherment

[ req ]

prompt                  = no
distinguished_name  = request_distinguished_name

[ request_distinguished_name ]

# You will want to change this, to match your target server.
# The order of these attributes is significant.
countryName     = US
#stateOrProvinceName    = California
organizationName    = Bannister @ Home
organizationalUnitName  = Testing
commonName      = $name
emailAddress        = preston@bannister.us

XXX

openssl req -config $config -new -keyout $base/private-key.pem -nodes -out requests/$name.pem

Again, the part you will want to change is in italics. For example: to generate a certificate request to server named “foozle” you would run the command:

sh do/request.sh foozle

Repeat the command for all your named entities.

Once you have created all certificate requests, have your CA sign all the certificates. All the parameters for processing certificate requests are in the file do/requests.sh:

test -d root/db || {
    mkdir root/db || exit 1
    touch root/db/index.txt
    echo 01 > root/db/serial
}
test -d history || mkdir history || exit 2
export issued=root/issued
test -d $issued || mkdir $issued || exit 3

export password1='really cool password'
export password2="$password1"

export config=history/ca.cnf

cat > $config << XXX

default_md      = sha1

# You will want to change this, when security matters.
input_password      = $password1
output_password     = $password2

[ ca ]

default_ca      = root_ca

[ root_ca ]

certs           = root/certificates
new_certs_dir       = $issued
crl_dir         = root/crl

database        = root/db/index.txt
serial          = root/db/serial
crlnumber       = root/db/crlnumber

certificate     = root/ca-certificate.pem
private_key     = root/ca-private-key.pem
crl         = root/crl.pem

default_days        = 365
default_crl_days    = 30
unique_subject      = no
#preserve       = no

policy          = root_ca_policy
x509_extensions     = root_ca_issued_certificate_extensions

[ root_ca_policy ]

countryName     = supplied
stateOrProvinceName = optional
organizationName    = supplied
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = supplied

[ root_ca_issued_certificate_extensions ]

basicConstraints    = CA:false

XXX

for request in `ls requests` ; do
    echo "===== Processing request: $request"
    export out="cn-`basename $request .pem`/certificate.pem"
    if openssl ca -config $config -batch -in requests/$request -key "$password1" > $out ; then
        mv requests/$request history/.
        echo "..... OK - Generated: $out"
    else
        rm $out
        echo "\n***** ERROR in processing request: $request\n"
    fi
done

Likely you do not to make any changes to the above. To process all the pending certificate requests, run:

sh do/requests.sh

The private key and public certificate for each named entity end up in the cn-* directories. The cn-foozle directory would contain the private key and public certificate for the “foozle” named entity.

The Makefile is meant as an example of creating the CA and certificates for all named entities in one operation.

#
#  Example makefile for creating a CA and certificates for named entities.
#

all : # default rule

NAMED=

m2003 m2008 z2003 z2008 john sammy alice

clean : clean-named ; @rm -rf root history requests
clean-named : ; @rm -rf cn-*

root : clean ; sh do/ca-create.sh
all : root ; for name in $(NAMED) ; do sh do/request.sh $$name ; done ; sh do/requests.sh

.PHONY : all clean clean-named

Nothing complicated here. Run make to create the CA and certificates for all named entities. Run make clean start over from a clean state. Note that the configuration files fed to OpenSSL are saved in the history/ directory. (Did I mention that I like to be thorough?)

blogroll

social