Create your own certificate authority with openssl - part one
This post is part one of a series on working with openssl to manage your own certificate authority (CA) for a couple of different use cases. If you just want to get a CA up and running for local development I thoroughly recommend mkcert.
In this first part we'll create a CA with openssl, install it into our browser and test it with a local web server. Note that I'll be providing all the information on the command line to make it easier to script things. To see it all tied together see this related github repo.
Create a certificate authority with OpenSSL
First we generate the private key for our CA (with a password other than 'capassword' preferably, and ensure it's not visible in your shell history).
> openssl genrsa -aes256 -passout pass:capassword -out test-ca.key 2048
Next we generate the root certificate. The fields in the subject name do not affect the certificate's validity. They are queryable and shown when the certificate is installed (firefox lists them by organisation) so use something that will remind you what it's for.
> openssl req -x509 -new -nodes \
-passin pass:capassword \
-key test-ca.key \
-sha256 -days 1825 \
-subj "/C=GB/O=robyoung.digital/CN=test" \
-out test-ca.pem
And we're done! Next we're going to create a server certificate and sign it with our shiny new CA key.
Create a server certificate
As with the CA, our first step is to generate a private key for our server. This time we don't want a password otherwise it becomes harder to install.
> openssl genrsa -out test-server.key 2048
Once we have a key we can generate our certificate signing request (CSR), just like when we're using a public CA. This time the common name is significant as clients will use it to determine whether the server is authorised to serve that domain.
> openssl req -new \
-subj "/C=GB/CN=dev.test" \
-key test-server.key \
-out test-server-static-csr.pem
If you need to support multiple domains with a single server certificate you have two options. Wildcard certificates, where you use a star (*) to match a domain name segment or subject alternative names (SANs).
The wildcard certificate approach is pretty simple.
> openssl req -new \
-subj "/C=GB/CN=*.dev.test" \
-key test-server.key \
-out test-server-wildcard-csr.pem
Subject alternative names (SAN) is an x509 extension that allows you
to add multiple domains to a single certificate. It is bit more complex
to implement on the command line. This
Stack Exchange answer helped me
figure out how to do it. The path to your openssl.cnf
is going to be system
dependent, on Linux it will probably be /etc/ssl/openssl.cnf
, however on Mac
(using a homebrew installed openssl) it is at /usr/local/etc/openssl/openssl.cnf
.
> openssl req -new \
-subj "/C=GB/CN=dev.test" \
-key test-server.key \
-out test-server-san-csr.pem \
-reqexts SAN \
-config <( \
cat /usr/local/etc/openssl/openssl.cnf \
<(printf "[SAN]\nsubjectAltName=DNS:one.dev.test,DNS:two.dev.test\n") \
)
While this is how you add SANs to a self signed key or a CSR you send to a public CA, there is unfortunately a bug in the x509 command that means extensions are not passed from certificate requests to certificates. For our purposes if we want SANs we're going to have to add them when we sign the CSR.
First, our basic certificate with no SANs. For more information about the extensions used see the openssl x509 config docs.
> openssl x509 \
-req -in test-server-static-csr.pem \
-passin pass:capassword \
-CA test-ca.pem -CAkey test-ca.key -CAcreateserial \
-out test-server-static-cert.pem \
-days 1825 -sha256 \
-extfile <( cat <<EOF
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
EOF
)
With the SANs it is almost identical but with the subjectAltName
extension.
> openssl x509 \
-req -in test-server-static-csr.pem \
-passin pass:capassword \
-CA test-ca.pem -CAkey test-ca.key -CAcreateserial \
-out test-server-san-cert.pem \
-days 1825 -sha256 \
-extfile <( cat <<EOF
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
subjectAltName=DNS:one.dev.test,DNS:two.dev.test
EOF
)
We now have a CA certificate and a couple of server certificates signed by that CA. It's time to test them out.
Test with Nginx and Curl
First let's spin up an Nginx docker container with our static certificate and server key. We're going to use a minimal ssl configuration, in real life you're going to want something much more robust.
nginx.vhost.conf
server {
listen 443 ssl;
server_name dev.test;
ssl_certificate /etc/nginx/ssl/certs/server-crt.pem;
ssl_certificate_key /etc/nginx/ssl/private/server.key;
location / {
return 200 'Static vhost test\n';
add_header Content-Type text/plain;
}
}
> docker run \
--rm -p 443:443 \
-v $(pwd)/nginx.vhost.conf:/etc/nginx/conf.d/default.conf:ro \
-v $(pwd)/test-server.key:/etc/nginx/ssl/private/server.key:ro \
-v $(pwd)/test-server-static-cert.pem:/etc/nginx/ssl/certs/server-crt.pem:ro \
nginx
Before we finally test this we're going to need dev.test
to point to 127.0.0.1
.
If we were just testing with cur we could make use of the --resolve
option to manually resolve. However, we also want this to work in a browser
so let's add it to our hosts file.
> echo "127.0.0.1 dev.test" >> /etc/hosts
Finally we're able test our certificate!
> curl --cacert test-ca.pem https://dev.test
Static vhost test
Install the certificate authority
In order to test this in a browser we need to install the CA certificate. This process varies between different platforms and can be tempremental (which is one of the reasons mkcert is so great). To make my life easier I'm going to describe adding the CA certificate to Firefox as it allows you to manage your authorities from it's own preferences.
First open your Firefox "Preferences". Select "Privacy & Security". Scroll all
the way to the bottom and click "View Certificates". Here we want to click the
tab called "Authorities" and then click "Import". Select our test-ca.pem
and
import.
You should now be able to visit https://dev.test in Firefox and see "Static vhost test" displayed. And we're done!
Next steps
This has been a bit of a monster post but we now have a CA certificate installed in our browser, a process for creating signed server certificates and a web server that tests this out, all created with easily scriptable commands. In the next post we'll create client certificates to identify ourselves with our servers.