You think you know something just enough to get by, until you have something that challenges your workflow and tools. Then you have to brush away the cobwebs, learn a few things, and work on some scripts. Based upon my recent adventures in dealing with EAP-TLS for wireless, I realized I was doing several things wrong with OpenSSL and my private certificate authority (CA) over the years. I never really spent any time reading good docs beyond creating cert requests and converting certificates, nor learning what all the options and extensions do.
I found a great instruction guide on how to properly set up not only a CA with OpenSSL, but an intermediate CA, revocation lists, and certificates for servers and users: https://jamielinux.com/docs/openssl-certificate-authority/. It goes a good job of explaining just the options you need and why.
Based on this guide and copious amounts of Googling to fix other problems I set about putting my CA house in order. Among other things, I wanted to make sure I was generating appropriate certs and finally get around to stop doing some things by hand. (And insert an intermediate CA to my setup for giggles because my home lab isn’t complicated enough Never mind, dealing with certificate chains and applications are annoying.)
Things I learned while yak shaving certificates, all in one place so hopefully other people can avoid my folly:
(disclaimer: I’m possibly wrong about some of this)
iOS needs a common name on certs to trust them: This is what started it all. On Apple iOS devices (unsure when this started, iOS 11?) if your private root CA certificate does not have a Common Name (CN) value set, you will not be able to trust the certificate on the device. Without a CN, the certificate just does not show up under General > About > Certificate Trust Settings > Enable Full Trust For Root Certificates. You can install the root certificate all day long, but you can’t get device-level trust without this.
Can’t just casually add a CN: Based on what I read for regenerating a root certificate with the same key, I thought I might be able to issue a new root cert and fix the CN. Turns out you can’t just add the CN on your existing root CA certificate because this breaks the chain of trust of any certificates you’ve signed with it. In particular, OpenSSL is going to throw an “unable to get local issuer certificate
” when it tries to verify a signed certificate against your newly altered root certificate:
# Existing cert, newly modified root, verification fails: # openssl verify -CAfile /opt/pki/CA/certs/wannnet-ca-20180913-cert.pem \ certs/raptor.wann.net-20170624-cert.pem certs/raptor.wann.net-20170624-cert.pem: C = US, ST = California, L = Fremont, O = wann.net, CN = raptor.wann.net, emailAddress = pki@wann.net error 20 at 0 depth lookup:unable to get local issuer certificate # Existing cert, original root, verification is OK: # openssl verify -CAfile /opt/pki/CA/certs/wannnet-ca-20170624-cert.pem \ certs/raptor.wann.net-20170624-cert.pem certs/raptor.wann.net-20170624-cert.pem: OK
This is because the Issuer: of our root certificates has changed and mismatched what the cert expects:
# Existing server certificate, signed with original root CA (without a CN): # openssl x509 -in certs/raptor.wann.net-20170624-cert.pem -text | grep Issuer Issuer: C=US, ST=California, L=Fremont, O=wann.net, OU=wann.net CA/emailAddress=pki@wann.net # Issuer: of original root CA certificate: # openssl x509 -in wannnet-ca-20170624-cert.pem -text | grep Issue Issuer: C=US, ST=California, L=Fremont, O=wann.net, OU=wann.net CA/emailAddress=pki@wann.net # Issuer: of new root CA certificate with a proper CN added: # openssl x509 -in wannnet-ca-20180913-cert.pem -text | grep Issue Issuer: C=US, ST=California, L=Fremont, O=wann.net, OU=wann.net CA, CN=wann.net Root CA/emailAddress=pki@wann.net
This means unless you re-issue every single certificate you have to reflect the new CN on the root cert, you’re going to be carrying around both root CA certificates in your devices’ trust stores until the child certificates eventually expire and you re-issue/re-sign them with the new root certificate. (2 years for me).
Upgrading signature algorithm on the root: However! Allegedly you can regenerate the root CA certificate with different signatures (the SHA1 -> SHA256 signature fracas recently) or new validity periods as long as you use the same key file you originally created the self signed cert with. You just can’t change the subject or CN details. (I wish I had known this before reissuing all of my client certs last year when I needed SHA256 signatures)
Adding subjectAltNames is messy: Somewhere along the way browsers such as Chrome started requiring valid Subject Alternative Name (SAN) on TLS certificates. For our purposes this is a list of DNS names (e.g. CNAMEs) that the certificate is valid with.
There’s not an easy way to add this to certificate requests (yet*), and every example on the Internet has you cracking open openssl.cnf
in a text editor every time you want a new cert (what could go wrong?!). For a while I did this, I couldn’t be bothered learning a better way and hated myself each time I made a cert. There’s also some convoluted bash “one-line” scripts out there that attempt to remedy this, but they’re hard to follow what they’re doing until you understand what they’re doing.
Of course my script is better than everyone else’s script: https://github.com/bwann/pki-tools/blob/master/make-wannnet-csrkey.sh. I’ve tried to simplify the bash and make it a little easier to understand. For the skeptical, here’s some sample output.
This script generates an RSA key and certificate request with the server name/common name as the first argument to the script, and any further arguments adds them to the request as Subject Alternative Names.
[1] As of OpenSSL 1.1.1 just released in September 2018, they’ve tweaked the req -extension
option to make this a bit easier: https://github.com/openssl/openssl/commit/bfa470a4f64313651a35571883e235d3335054eb
CAs drop subjectAltNames when signing: By default OpenSSL will drop any user-submitted extensions (such as subjectAltNames
) from a certificate request when it comes time to sign the certificate with your CA. This means when you sign a certificate request that includes your alternative names, this undoes all the work you just did to add them. Now you’ve got to re-supply the subjectAltNames
via the OpenSSL config somehow as seen above for the signing process. There’s a good reason for this behavior, preventing unwanted user input: a rogue user could submit a certificate request with an extension something like basicConstraints=CA:TRUE
, and unless it’s caught at signing time, the root has just issued a CA certificate.
You can get around this in your own CA environment by configuring copy_extensions
in your openssl.cnf
under the CA_default
section. There’s a couple of options for this and the man page (man ca
) has clear warnings about the implications. By setting this to copy_extensions=copy
, this will copy the subjectAltNames
from the certificate request; however you will want to make sure whichever extensions you’re using to sign a certificate, you’ve already nailed down basicConstraints
and keyUsage
in them so the extensions from the request don’t try to overwrite them.
Key extensions for servers and clients: This is what I first learned while setting up certificates for EAP-TLS. There’s extensions you can add to the X509 certificate that tell what the intended purpose of the certificate is (man x509v3_config
). I haven’t ever had to use these because Linux has always been pretty happy (blissfully ignorant?) with what I was using before. Apparently things like Windows and Android cares about these extensions, i.e. they expect a server cert on the server, a client cert on the client app.
Ideally in your openssl.cnf
you’d have a section defining options to use while signing a certificate for servers (e.g. [ server_cert ]
, and another section for client/user certificates (e.g. [ user_cert ]
, each containing the appropriate extendedKeyUsage
settings for each type. Then when it comes time to sign a cert, tell OpenSSL which extension to use.
Android needs magic to install a root CA with system level trust. I haven’t gone through the effort to figure this one out yet, every time I install my root CA certificate on my Nexus it only winds up as a user-level cert, and displays a fabulous “warning third parties are snooping on your network” notification. From what I gather some root-level muckery needs to happen. Surely some MDM software has figured this out already.
Links
https://github.com/bwann/pki-tools/ : Some of my own scripts for generating cert requests, keys, signing them with an OpenSSL CA. Server-centric for now, still ironing out the kinks in user certificates for now and will post whenever they’re decent.
https://www.phildev.net/ssl/ : A friend’s guide to X509 certs that I had forgotten about until I posted this
https://jamielinux.com/docs/openssl-certificate-authority/ : The other CA setup guide I mentioned at the beginning of this post