Expired CA Notes

Posted on Mon 31 October 2022 in hints

Recently, I ran some tests to see what would happen when my root CA cert expired, and what I'd need to do to update the cert.

Spoiler alert: Updating the CA cert was not that hard...

First I created a CA that expired in 2 hours using the new-ca.py code below:

from OpenSSL import crypto

#Following script will create a self signed root ca cert.
from OpenSSL import crypto, SSL
from os.path import join
import random

CN='expired-ca-test'
pubkey = "%s.crt" % CN #replace %s with CN
privkey = "%s.key" % CN # replcate %s with CN

pubkey = join(".", pubkey)
privkey = join(".", privkey)

k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 2048)

# create a self-signed cert
cert = crypto.X509()
cert.get_subject().CN = CN
cert.set_serial_number(0)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(7200)  # CA is only good for 2 hours
cert.set_issuer(cert.get_subject())
cert.set_subject(cert.get_subject())
cert.set_version(2)
xt = crypto.X509Extension(b'basicConstraints',1,b'CA:TRUE')
cert.add_extensions((xt,))

cert.set_pubkey(k)
cert.sign(k, 'sha512')
pub=crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
priv=crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
open(pubkey,"wt").write(pub.decode("utf-8"))
open(privkey, "wt").write(priv.decode("utf-8") )

This block is based on how the ancient certmaster program created its CA.

Then I created a expired-ca-test.srl with contents "01"

> echo 01 > expired-ca-test.srl

Then I issued a cert against this CA:

> openssl genrsa -out pre-expired-example.key 4096
> openssl req -new -key pre-expired-example.key -out pre-expired-example.csr
> openssl x509 -req -days 365 -in pre-expired-example.csr -CA expired-ca-test.crt  -CAkey expired-ca-test.key  -CAserial expired-ca-test.srl -out pre-expired-example.crt
> openssl x509 -in pre-expired-example.crt -text
> openssl verify -verbose -CAfile expired-ca-test.crt pre-expired-example.crt
pre-expired-example.crt: OK

Then I waited 2 hours and went back to check the certs:

> openssl x509 -in expired-ca-test.crt -noout -enddate
notAfter=Oct  2 17:41:11 2022 GMT

Then I tested what would happen if I tried verifying the cert signed with the expired CA:

> openssl verify -verbose -CAfile expired-ca-test.crt pre-expired-example.crt
CN = expired-ca-test
error 10 at 1 depth lookup: certificate has expired
error pre-expired-example.crt: verification failed

THIS FAILED. I thought previously signed keys would continue to verify against the expired CA but new certs wouldn't be created. Instead previously signed certs won't validate against the expired CA.

Then I tried signing a new cert with the expired CA. Certainly this fail, right ?

> openssl genrsa -out expired-example.key 4096
> openssl req -new -key expired-example.key -out expired-example.csr
> openssl x509 -req -days 365 -in expired-example.csr -CA expired-ca-test.crt -CAkey expired-ca-test.key -CAserial expired-ca-test.srl -out expired-example.crt

THIS WORKED in that it created the cert, though verification still fails:

> openssl verify -verbose -CAfile expired-ca-test.crt expired-example.crt
CN = expired-ca-test
error 10 at 1 depth lookup: certificate has expired
error expired-example.crt: verification failed

Now lets see what happens if we update the CA cert with the update-ca.py script below.

This is almost the same as the new-ca.py script above, except the original CA key is reused instead of generating a new key. Also the CN and serial number need to be the same as the original expired CA cert.

Verification will fail if the CN or serial number values are not the same as the original CA, but unfortuanetly I didn't save the errors from when I tried using 'updated-ca-test' as the CN, or when I tried bumping up the serial number to 1.

from OpenSSL import crypto

#Following script will create a self signed root ca cert.
from OpenSSL import crypto, SSL
from os.path import join
import random

CN='updated-ca-test'
pubkey = "%s.crt" % CN #replace %s with CN
privkey = "%s.key" % CN # replcate %s with CN

pubkey = join(".", pubkey)
privkey = join(".", privkey)

# Instead of creating a new key, use the old CA's key
# nope: k = crypto.PKey()
# nope: k.generate_key(crypto.TYPE_RSA, 2048)
st_key=open('expired-ca-test.key', 'rt').read()
k = crypto.load_privatekey(crypto.FILETYPE_PEM, st_key)

# create a self-signed cert
cert = crypto.X509()
cert.get_subject().CN = 'expired-ca-test'  # keep the same CN as the old CA cert
cert.set_serial_number(0)                  # keep the same serial number as the old CA cert
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(63072000)  # CA is only good for 2 years
cert.set_issuer(cert.get_subject())
cert.set_subject(cert.get_subject())
cert.set_version(2)
xt = crypto.X509Extension(b'basicConstraints',1,b'CA:TRUE')
cert.add_extensions((xt,))

cert.set_pubkey(k)
cert.sign(k, 'sha512')
pub=crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
priv=crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
open(pubkey,"wt").write(pub.decode("utf-8"))
open(privkey, "wt").write(priv.decode("utf-8") )

Note that this code creates a updated-ca-test.key that's the same as expired-ca-test.key, so I could have continued using expired-ca-test.key in the cert creation below.

> diff expired-ca-test.key updated-ca-test.key
> echo $?
0

Next I created an updated-ca-test.srl file. I could have continuned using expired-ca-test.srl

> cp expired-ca-test.srl updated-ca-test.srl

Now let's see if the new CA can be used to create a new cert:

> openssl genrsa -out post-expired-example.key 4096
> openssl req -new -key post-expired-example.key -out post-expired-example.csr
> openssl x509 -req -days 365 -in post-expired-example.csr -CA updated-ca-test.crt  -CAkey updated-ca-test.key  -CAserial updated-ca-test.srl -out post-expired-example.crt
> openssl x509 -in post-expired-example.crt -text
> openssl verify -verbose -CAfile updated-ca-test.crt post-expired-example.crt
post-expired-example.crt: OK

Now verify the old cert verifies using the new CA:

> openssl verify -verbose -CAfile updated-ca-test.crt pre-expired-example.crt
pre-expired-example.crt: OK

THIS WORKED. The updated CA could be used to verify both new and previous created certs Hurray !!

Conclusion

An expired/expiring root CA may be a hassle, but it's not catastrophic. The biggest pain should be pushingout the updated root CA everywhere the cert is being used in your environment. If you're using an orchestration/CM tool like Salt or Ansible, updating the root CA cert shouldn't be too bad, but remember to reload or restart any services using the cert to force the updated CA cert to read.