Skip to main content

Creating SSL Certificates

Published

A million people have written about this but now it’s my turn. I have a need to create for myself a certificate authority and then to use that certificate authority so that my programs can communicate with each other using those certificates to identify themselves. Here are the commands that I ran with absolutely no explanation behind why or how they work.

I decided to not screw around with RSA and instead I jumped straight to ECC. Everything I’m doing is controlled by me so I have no need for the strong backward compatibility that RSA provides.

Please also note that you will need to take my examples and fix your paths as appropriate. I keep my certificates in /usr/local/ssl/certs. The certificate authority is in there and then all the generated certificates are kept in /usr/local/ssl/certs/local. So you’ll need to double-check the paths in my following commands.

Before we can create certificates we need a certificate authority. The first step for that is to create the certificate authority private key:

openssl ecparam -out /usr/local/ssl/certs/local-ca.key -name prime256v1 -genkey

That key will be protected with my life, metaphorically. I filled in some values with my details like my location and my name. Whatever, no big deal. Just make it somewhat accurate because it will be pasted into every certificate that you sign from this CA.

Then I must create my certificate authority public certificate:

openssl req -x509 -new -nodes -days 36500 -sha512 \
    -key /usr/local/ssl/certs/local-ca.key \
    -out /usr/local/ssl/certs/local-ca.cert

I set it to exist for 36,500 days, or about ten years. Choose a number that you feel comfortable with.

Now I can start to sign certificates from this. First I must create an OpenSSL configuration file. Mine looks like this:

[ca]
default_ca                      = CA_default

[CA_default]
database                        = certificates
new_certs_dir                   = /usr/local/ssl/certs/local
certificate                     = /usr/local/ssl/certs/local-ca.cert
private_key                     = /usr/local/ssl/certs/local-ca.key
preserve                        = no
email_in_dn                     = no
nameopt                         = default_ca
certopt                         = default_ca
policy                          = policy_match
default_days                    = 2190

[policy_match]
countryName                     = match
stateOrProvinceName             = match
organizationName                = match
organizationalUnitName          = match
commonName                      = supplied
emailAddress                    = match

[req]
string_mask                     = nombstr
distinguished_name              = req_distinguished_name
req_extensions                  = v3_req
x509_extensions                 = v3_req

[req_distinguished_name]
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
emailAddress                    = Email Address
emailAddress_max                = 40
localityName                    = City
stateOrProvinceName             = State
countryName                     = Country Code
countryName_min                 = 2
countryName_max                 = 2
commonName                      = Common Name
commonName_max                  = 64

0.organizationName_default      = Your Name
organizationalUnitName_default  = Certificate Authority
localityName_default            = Your City
stateOrProvinceName_default     = Your State
countryName_default             = US
emailAddress_default            = you@example.com

[v3_ca]
basicConstraints                = CA:TRUE
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid:always,issuer:always

[v3_req]
subjectKeyIdentifier            = hash
basicConstraints                = critical,CA:FALSE
keyUsage                        = critical,digitalSignature,keyEncipherment

There’s nothing particularly sensitive in this file. It’s just there. Your certificates will copy their locality and state and country from your certificate authority and not from the configuration file.

With that file and the certificate authority that we created we can start signing certificates with three commands. First, we want to create our certificate’s private key:

openssl ecparam -name prime256v1 -genkey \
    -out /usr/local/ssl/certs/local/example.com.key

With the private key and the above configuration file we will create a certificate signing request.

openssl req -new \
    -key /usr/local/ssl/certs/local/example.com.key \
    -out /usr/local/ssl/certs/local/example.com.csr \
    -batch -subj "/CN=example.com" -sha512 \
    -config /usr/local/ssl/certs/local-openssl.conf

Those two commands – creating the private key and the signing request – only need to be done once, ever. If you keep those two files then all you need to do when your certificate expires is use the existing signing request to create a new certificate and it will be created with all of the same options. Now to create that new certificate:

openssl x509 -req -extfile <(printf "subjectAltName=DNS:example.com") \
    -in /usr/local/ssl/certs/local/example.com.csr \
    -out /usr/local/ssl/certs/local/example.com.cert \
    -CA /usr/local/ssl/certs/local-ca.cert \
    -CAkey /usr/local/ssl/certs/local-ca.key \
    -CAcreateserial -sha512

That’s it. That’s the entire process. And this even follows RFC2818 and sets a subjectAltName parameter so that clients don’t complain.

Be aware that SSL is a tricky thing. Some of my options might not be 100% correct and some of them might not age well so it’s really best to double-check what you run before you run it and not just trust me. But if you’re lazy then these commands will probably work.

If you want to let someone else do the hard work for you and have it work in your browser, though, I highly recommend that you use Let’s Encrypt to automatically sign certificates. I wrote about how to get that going on my blog in the past.