Working with OpenSSL and DNS alternative names
Why This Story
In Today’s world in some case you would want your certificates to be able to be legitimate for more then one domain. For that purpose we can apply DNS alternative names to our SSL certificates.
A good example for that is when you setup a website on OpenShift and you will want your certificate to be valid for both Openshift “apps.<cluster name>.<you Domain>” prefix and for your domain that you bought for your application (“app.exmaple.com” for example).
This tutorial
In our tutorial I will setup a certificate for my docker registry and at the end I will show additional step due to the way the docker command works.
Answer files
When running the “openssl” command without an answer file the command will ask use to feel in the blanks (unless we set then up in openssl.cnf in advanced). To same use time we will start by creating 2 answer files , one for the CA and one for our certificate , the reason for the separation is that the CA should not have alternatives names given to him at the certificate creation.
First we set a few environment variables :
# export DOMAIN="example.local"
# export SHORT_NAME="registry"
will write an answer file for our registry (domain) :
$ cat > ${SHORT_NAME}_answer.txt << EOF
[req]
default_bits = 4096
prompt = no
default_md = sha256
x509_extensions = req_ext
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C=US
ST=New York
L=New York
O=MyOrg
OU=MyOrgUnit
emailAddress=me@working.me
CN = ${SHORT_NAME}
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = ${SHORT_NAME}
DNS.2 = ${SHORT_NAME}.${DOMAIN}
EOF
(you can change the dn values as you please except for the “CN”)
Next we will create the CA answer file which we will use (as mentioned) only for the CA creation.
$ cat > csr_ca.txt << EOF
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = usr_cert
[ dn ]
C=US
ST=New York
L=New York
O=MyOrg
OU=MyOU
emailAddress=me@working.me
CN = server.example.com
[ usr_cert ]
basicConstraints=CA:TRUE
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
EOF
Feel free to change the DN and the DNS values as you see fit. Make sure that the first DNS matches the Domain CN.You can apply the CA answer file to your domain in case you don’t need the alternative names options.
CA certificate and Key
We will start by creating the files we need for our CA. as a why of work we will always start with generate the RSA key with the length of 4096 (at the very list) .
Generate the Key:
$ openssl genrsa -out ca.key 4096
Next we will use the CA key we just created and the ca answer file to generate our CA certificate (that will be our public CA we will send to every machine that will want to connect to our registry over SSL.
Generate the CA
$ openssl req -new -x509 -key ca.key -days 730 -out ca.crt -config <( cat csr_ca.txt )
One the command was successful you can run “ls” and see the 2 files we created :
- ca.key
- ca.crt
Server Certificate and Key
for the following step we will create 2 additional files for our server (registry). One is (and obviously) the Server key and the other is the server certificate request.
Sense we need the CA to generate (and verify) our server certificate we are creating a request file so the CA will read for certificate details.
Generate Server Key
Same as we done for the CA , we are generating an RSA key with the length of 4096 chars.
$ openssl genrsa -out ${SHORT_NAME}.key 4096
Generate Server CSR
Now we will generate the certificate request using the domain Key and the domain answer file which we created in the beginning of the this tutorial.
$ openssl req -new -key ${SHORT_NAME}.key -out ${SHORT_NAME}.csr -config <( cat ${SHORT_NAME}_answer.txt )
It is a very good practice at this point to Test the CSR for DNS alternative names :
$ openssl req -in ${SHORT_NAME}.csr -noout -text | grep DNS
DNS:registry, DNS:registry.example.local
If you received the output as in the example you are good to go.
Sign the CSR :
now comes the tricky part , we need to tell the CA to use the “altrnames” we setup in the answer file but we need to tell it which section to look at for the values we need so we are going to add 2 more arguments for this purpose.
$ openssl x509 -req -in ${SHORT_NAME}.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ${SHORT_NAME}.crt -days 730 -extensions 'req_ext' -extfile <(cat ${SHORT_NAME}_answer.txt)
as you can see there are 2 more arguments :
- extentions — section from config file with X509V3 extensions to add
- extfile — configuration file with X509V3 extensions to add
Only when we team up those 2 options does our CA sign the certificate with our alternatives DNS names.
Certificate bundle
In some cases it is a good practice to join the certificate and the CA into a single file (not all servers has a CA configuration options).
$ mv ${SHORT_NAME}.crt ${SHORT_NAME}-certonly.crt
$ cat ${SHORT_NAME}-certonly.crt ca.crt > ${SHORT_NAME}.crt
Testing the Certificate
Now all that is left to do is to test our certificate :
$ openssl x509 -in ${SHORT_NAME}.crt -noout -text | grep DNS
DNS:registry, DNS:registry.example.local
And if we want to make sure the ca.crt is the signer of the certificate we can test it with the “verify” arguments:
$ openssl verify -CAfile ca.crt ${SHORT_NAME}.crt
registry.crt: OK
If your output is the same as the example you done everything right!!
updating the Registry
As promise to update the registry first we will copy our ca.crt to our “anchors” directory :
$ cp ca.crt /etc/pki/ca-trust/source/anchors/${SHORT_NAME}.crt
$ update-ca-trust extract
For the registry we will copy the file to our domain directory under “/etc/docker/cert.d/” as follow :
$ export MY_SERVER="registry.example.local"
$ mkdir /etc/docker/certs.d/${MY_SERVER}
$ cp ca.crt /etc/docker/certs.d/${MY_SERVER}/
Now all that is left is to restart the docker service and we are good to go.
$ systemctl restart docker
That is it
Have FUN!!!