A million people have written about this and now it’s my turn. I have a need to create 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 controlled by me so I have no need for strong backward compatibility that RSA provides.
Also note that you’ll 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 of the generated certificates are kept in
/usr/local/ssl/certs/local. So you’ll need to double check the paths in my following commands.
First I created my 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 -key /usr/local/ssl/certs/local-ca.key -days 36500 -sha512 -out /usr/local/ssl/certs/local-ca.cert
I set it to exist for 36,500 days, aka ten years. Choose a number that works for you.
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 = firstname.lastname@example.org [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 CA. 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.