ETOOBUSY 🚀 minimal blogging for the impatient
Intermediate CA Solution
TL;DR
Tired of the long march to a valid Intermediate CA? We’re at the end of our journey. In real TL;DR spirit, just look at OpenSSL Certificate Authority. Or read on.
We learned that Intermediate CAs are hard!. We figured out why with our Intermediate CA Investigation. Now it’s time for solutions.
The big inspirer for whatever you find here is the website OpenSSL Certificate Authority. Big kudos, very clear.
Use openssl ca, Luke!
The bottom line is that we need to fit those x509v3 extensions at least
in the Intermediate CA certificate, and to do this we cannot just sign its
certificate request with openssl x509. At least, I didn’t find a way to do
this.
We have to resort to another sub-command: ca. This time, anyway, we want
to take full control of calling it, i.e. we don’t want to rely upon defaults
that may vary from distribution to distribution, or even across different
versions of the same distribution. It’s time that we use
configuration files.
Root CA
This is our minimal configuration file for the Root CA, aptly named
rca.cnf:
[ ca ]
default_ca = CA_default
[ CA_default ]
new_certs_dir = .
database = rca.x.database
serial = rca.x.serial
RANDFILE = rca.x.RANDFILE
private_key = rca.key
certificate = rca.crt
default_md = sha256
default_days = 42
preserve = no
policy = policy
copy_extensions = copy
[ policy ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 2048
prompt = no
distinguished_name = distinguished_name
string_mask = utf8only
default_md = sha256
x509_extensions = rca_extensions
[ distinguished_name ]
countryName = IT
stateOrProvinceName = RM
localityName = Roma
organizationName = Everish
organizationalUnitName = Root
commonName = Everish Root CA
[ rca_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical,digitalSignature,cRLSign,keyCertSign
[ ica_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,digitalSignature,cRLSign,keyCertSign
A lot of the stuff is mandatory so it’s there for this reason. E.g.
section policy, or a bunch of filename in CA_default.
Section req is read when issuing the req sub-command, which happens when
we generate the self-signed certificate for the Root CA:
openssl req -x509 -new -config rca.cnf -out rca.crt -days 42 \
-newkey rsa:2048 -nodes -keyout rca.key
Note that we’re setting -config to point to our file rca.cnf.
The -x509 parameter instructs on using the x509_extensions which, in our
case, map onto section rca_extensions in the configuration file. This
turns on the flags for being a CA on the root certificate too, even though
we saw that at least curl does not seem to be picky about this. Better
play it safe and future proof, anyway.
The ca section is for the ca sub-command, as you might already have
guessed. This will be discussed a bit down on the road, tough. The same goes
for the ica_extensions section. Be patient!
Intermediate CA
At the very last, we’re there! We will first use the following ica.cnf
configuration file to generate a certificate request file:
[ ca ]
default_ca = CA_default
[ CA_default ]
new_certs_dir = .
database = ica.x.database
serial = ica.x.serial
RANDFILE = ica.x.RANDFILE
private_key = ica.key
certificate = ica.crt
default_md = sha256
default_days = 42
preserve = no
policy = policy
copy_extensions = copy
[ policy ]
countryName = supplied
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 2048
prompt = no
distinguished_name = distinguished_name
string_mask = utf8only
default_md = sha256
[ distinguished_name ]
countryName = IT
stateOrProvinceName = RM
localityName = Roma
organizationName = Everish
organizationalUnitName = Intermediate
commonName = Everish Intermediate CA
[ srv_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:false
keyUsage = critical,digitalSignature,keyEncipherment
The structure is similar to the rca.cnf file, only we have a bit less
configurations. Section srv_extensions will be used later together with
the ca sub-command and section.
Let’s generate the certificate request then:
openssl req -new -config ica.cnf -out ica.csr -days 42 \
-newkey rsa:2048 -nodes -keyout ica.key
This triggers the req section in the ica.cnf file, which sets the right
Common Name, etc. Time for signing from the Root CA:
openssl ca -batch -config rca.cnf -extensions ica_extensions -days 42 \
-in ica.csr -out ica.crt
We are using the ca sub-command with rca.cnf here, because we are
putting the “hat” of the Root CA in this signing action. We are also using
explicitly the ica_extensions here, i.e. from the rca.cnf file:
[ ica_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,digitalSignature,cRLSign,keyCertSign
This means that the certificate is set as a CA:true one (alas!), but also
that it can not be used to create further CAs (due to pathlen:0). In
this way, the Root CA retains its capabilities, while only delegating the
Intermediate CA to sign client/server certificates.
This is the resulting certificate:
Certificate:
...
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=IT, ST=RM, L=Roma, O=Everish, OU=Root, CN=Everish Root CA
...
Subject: C=IT, ST=RM, O=Everish, OU=Intermediate, CN=Everish Intermediate CA
X509v3 extensions:
X509v3 Subject Key Identifier:
03:4E:37:FD:8C:84:E2:E3:64:42:EE:55:75:3A:D1:B1:5C:04:E4:B2
X509v3 Authority Key Identifier:
keyid:DE:2A:AB:95:54:9F:A6:56:34:2B:13:B1:CE:9D:B1:30:CA:37:11:9B
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:0
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
...
At the very last we have a good certificate for the Intermediate CA!
Server Certificate?
Now that we have unlocked the power of configuration files for OpenSSL,
why stop here? We can use use one also for generating our certificate
request for the server using srv.cnf:
[ req ]
default_bits = 2048
prompt = no
distinguished_name = distinguished_name
string_mask = utf8only
default_md = sha256
[ distinguished_name ]
countryName = IT
stateOrProvinceName = RM
localityName = Roma
organizationName = Everish
organizationalUnitName = Server
commonName = srv.example.com
[ extensions ]
subjectAltName = DNS:localhost,DNS:srv.example.com
While the stuff in distinguished_name might be put inside the command
line, the extensions section is interesting because it allows us to set
some extensions also in a server’s (or client’s) certificate.
Let’s generate the request then:
openssl req -new -config srv.cnf -out srv.csr -days 42 \
-reqexts extensions -newkey rsa:2048 -nodes -keyout srv.key
Did you take note of the -reqexts option set to extensions? Here we just
set the name of the corresponding section inside srv.cnf to make sure that
the subjectAltName finds its way inside the request:
Certificate Request:
Data:
...
Subject: C=IT, ST=RM, L=Roma, O=Everish, OU=Server, CN=srv.example.com
...
Requested Extensions:
X509v3 Subject Alternative Name:
DNS:localhost, DNS:srv.example.com
...
This subjectAltName is a very handy option that allows us to mark the
certificate as valid for a variety of names, instead of the Common Name only
as it would be by default. And yes… we have to put the Common Name in the
list too.
Let’s sign it with our Intermediate CA. Again, this time we use the more
powerful ca sub-command, leveraging the Intermediate CA configuration
file ica.cnf to do this (because we have to put on the Intermediate CA
hat when doing the signing):
openssl ca -batch -config ica.cnf -extensions srv_extensions -days 42 \
-in srv.csr -out srv.crt
This time we are asking to take the srv_extensions section of ica.cnf,
i.e. the following:
[ srv_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:false
keyUsage = critical,digitalSignature,keyEncipherment
This will make sure that the certificate cannot sign other certificates
(i.e. it’s a leaf in our tree of signatures). This isn’t sufficient,
though: we also have to make sure that other extensions in the request (like
the subjectAltName we saw above) make their way into the generated
certificate, which is why we have this in ica.cnf:
[ CA_default ]
...
copy_extensions = copy
Let’s take a look at the generated certificate then:
Certificate:
...
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=IT, ST=RM, O=Everish, OU=Intermediate, CN=Everish Intermediate CA
...
Subject: C=IT, ST=RM, O=Everish, OU=Server, CN=srv.example.com
...
X509v3 extensions:
X509v3 Subject Key Identifier:
D3:43:48:62:D1:E8:DB:2D:AF:44:C6:48:76:5C:AD:5A:F3:11:E4:B9
X509v3 Authority Key Identifier:
keyid:21:45:8F:0B:7C:6B:12:17:43:EA:02:B8:B2:2A:0C:28:3B:BB:C8:0E
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
DNS:localhost, DNS:srv.example.com
...
How amazing! Both extensions from the certificate request (X509v3 Subject
Alternative Name) and from the Intermediate CA (basically, all the other
ones) are included.
Give it a try!
If you want to try this in action, use version 1.2 (or later) of the
polettix/certificate-example image, which now includes a
right-intermediate sub-directory! This should get you started, hopefully:
Happy learning!