Control Let's Encrypt's Certificate Rekey Interval

This article details the commands used to administratively control the rekey and renewal behaviors of Let's Encrypt's certbot.

Detailed will be how to create the private/public key pair and certificate signing request manually and then request the Let's Encrypt certificate with certbot's certonly command line option. This essentially avoids the automation of the renew command requiring that we perform the various steps manually.

Certbot's renew command by default generates a new private/public key pair and certificate signing request for each renewal. This essentially means cerbot is rekeying the certificate with each renewal. This is perfectly acceptable solution for most use cases of Let's Encrypt certificates. However, rekeying can optimally be done less frequently such as once a year or longer. Certbot's certonly command line option allows you to specify your own certificate signing request instead of accepting one automatically generated by Certbot's renew command. At each renewal interval, the same CSR is used to request a new (renewed) certificate.

One specific reason one might choose this method, would be to maintain the same unchanging public key of a certificate. DANE TLSA DNS resource records are a hash of a certificate's public key and would need to be updated every time the public key changes. Let's Encrypt certificates are valid for only 3 months which would require updating the respective TLSA DNS RRs just as frequently. Manual rekeying can be scheduled to occur over longer time spans, respective TLSA DNS RRs would only need to be updated at that time.

 

Manually Create a Key Pair, CSR and Request a New Certificate

Start by creating and cd into a directory to work in then generate a private/public key pair.

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa-privkey.pem

openssl rsa -pubout -in rsa-privkey.pem -out rsa-pubkey.pem

 

Next create a stripped down version of openssl.cnf based on the following template.

[ req ]
prompt = no
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext

[ req_distinguished_name ]
CN = example.com

[ req_ext ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = example.org
DNS.4 = www.example.org
IP.1 = 192.168.0.1
email.1 = This email address is being protected from spambots. You need JavaScript enabled to view it.

In section [ req_distinguished_name ] change Common Name CN = example.com to the FQDN of your server.
In section [ alt_names ] include at least one DNS entry matching the FQDN of the server.

Optionally add any additional FQDN DNS entries that the server uses. You can also add IP address(es) as well as an email address to include in the certificate. Examples are provided in the template above. Only one DNS.1 entry is required, the rest are optional.

 

Generate the certificate signing request with the custom openssl.cnf.
openssl req -new -sha256 -key rsa-privkey.pem -nodes -config openssl.cnf -outform pem -out rsa-csr.pem

 

Request a new certificate with the new CSR.
certbot certonly --webroot -w /usr/local/www/apache24/data --csr rsa-csr.pem

 

Optionally if you'd like to view and confirm the contents of the CSR in plain text:
openssl req -in rsa-csr.pem -noout -text

To view the contents of a certificate in plain text:
openssl x509 -in 0001_chain.pem -noout -text

 

Upon successful execution certbot will drop three certificates in the current working directory.
0000_cert.pem equivalent to cert.pem
0000_chain.pem equivalent to chain.pem
0001_chain.pem equivalent to fullchain.pem

 

Rename the certificate files if desired and restart the web server to begin using the new certificate. With this method you now have the option to continue using the same CSR repeatedly for ongoing certificate renewals. Rekey the certificate at your desired long-term interval by re-rolling new keys and a new CSR just prior to the next renewal request.

 

A Scripted Solution For Automation

I am providing below the two scripts that I am personally using in my production environment.

Several important things to note:

  • The scripts have several run-time dependencies such as BASH and BIND being installed.
  • Please comb through and edit the scripts to suit your environment.
  • The scripts include the DANE TLSA RR management using nsupdate to update a local BIND name server. This is optional and can be edited out.
  • The cerbot command line in these scripts specifies --webroot authentication. Substitute this with your preferred authentication method.
  • When testing the scripts ensure to insert the --dry-run directive into each of the certbot certonly command lines.

The purpose of rekey-cert script is to roll a new private / public key pair and create a new certificate signing request from this key pair. This script should be run at some extended interval of time such as once a year.

The renew-cert script renews the certificate by requesting a new one with the existing CSR. Renew-cert also checks for the existence of the rekey/ directory. If it exists, the script will move the new CSR and key files into place just prior to running the cerbot certificate request. This script will be run every two months.

 

To begin, create a directory at any desired location and copy into it openssl.cnf created in the previous section and the following two scripts.

rekey-cert

#! /usr/local/bin/bash
# rekey-cert v1.01
set -e
mydir=$(dirname $(readlink -f $0))
cd $mydir

# Create rekey/ directory
mkdir ./rekey/
cd rekey/

## Create a new RSA key pair
# Private Key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa-privkey.pem
# Public Key
openssl rsa -pubout -in rsa-privkey.pem -out rsa-pubkey.pem

# Generate a new CSR
openssl req -new -sha256 -key rsa-privkey.pem -nodes -config ../openssl.cnf -outform pem -out rsa-csr.pem

# Save the new TLSA hash
openssl req -noout -pubkey -in rsa-csr.pem | openssl rsa -pubin -outform DER | sha256 > tlsa311

# Add the new TLSA 3 1 1 DNS Resource Records
NEWTLSA=`cat tlsa311`
echo server 192.168.0.1 > nsupdate
echo update add _443._tcp.example.com. 86400 IN TLSA 3 1 1 $NEWTLSA >> nsupdate
echo update add _443._tcp.www.example.com. 86400 IN TLSA 3 1 1 $NEWTLSA >> nsupdate
echo send >> nsupdate
echo update add _443._tcp.example.org. 86400 IN TLSA 3 1 1 $NEWTLSA >> nsupdate
echo update add _443._tcp.www.example.org. 86400 IN TLSA 3 1 1 $NEWTLSA >> nsupdate
echo send >> nsupdate
echo quit >> nsupdate
nsupdate -k ../ddns-key nsupdate
rm -f nsupdate

renew-cert

#! /usr/local/bin/bash
# renew-cert v1.02
set -e
mydir=$(dirname $(readlink -f $0))
cd $mydir

if { ! -f rsa-privkey.pem ]; then

        # A private key doesn't exist yet therefore this is the first-run
        # Execute rekey-cert to create initial key pair and CSR
        ./rekey-cert
fi
        
if [ ! -d "rekey/" ]; then

        # If the rekey/ directory doesn't exist then simply
        # renew LetsEncrypt certificate with the existing CSR

        certbot certonly --webroot -w /usr/local/www/apache24/data --csr rsa-csr.pem -n

else

        # If the rekey/ directory does exist then perform additional rekey related
        # tasks and renew LetsEncrypt certificate with the new CSR

        if [ -f rsa-privkey.pem ]; then

                # Backup existing CSR and key files
                mv -f rsa-privkey.pem rsa-privkey.bak
                mv -f rsa-pubkey.pem rsa-pubkey.bak
                mv -f rsa-csr.pem rsa-csr.bak
                mv -f tlsa311 tlsa311.bak
        fi

        # Move all files from rekey/ to current directory then remove rekey/
        mv -f rekey/* ./
        rm -rf rekey/

        # Renew LetsEncrypt Certificate With New CSR
        certbot certonly --webroot -w /usr/local/www/apache24/data --csr rsa-csr.pem -n

        if [ -f tlsa311.bak ] && [ -f 0000_cert.pem ]; then

                # Remove Prior TLSA 3 1 1 DNS Resource Records
                OLDTLSA=`cat tlsa311.bak`

                echo server 192.168.0.1 > nsupdate
                echo update delete _443._tcp.example.com. 86400 IN TLSA 3 1 1 $OLDTLSA >> nsupdate
                echo update delete _443._tcp.www.example.com. 86400 IN TLSA 3 1 1 $OLDTLSA >> nsupdate
                echo send >> nsupdate
                echo update delete _443._tcp.example.org. 86400 IN TLSA 3 1 1 $OLDTLSA >> nsupdate
                echo update delete _443._tcp.www.example.org. 86400 IN TLSA 3 1 1 $OLDTLSA >> nsupdate
                echo send >> nsupdate
                echo quit >> nsupdate
                nsupdate -k ddns-key nsupdate
                rm -f nsupdate

        fi
fi

if [ -f 0000_cert.pem ]; then

        if [ -f cert.pem ] || [ -f fullchain.pem ]; then
                # Backup existing certificates
                mv -f cert.pem cert.bak
                mv -f chain.pem chain.bak
                mv -f fullchain.pem fullchain.bak
        fi

        # Rename the newly acquired certificates
        mv -f 0000_cert.pem cert.pem
        mv -f 0000_chain.pem chain.pem
        mv -f 0001_chain.pem fullchain.pem

        # Restart Apache to apply the changes
        service apache24 restart
        echo Certificate Renewal Successful! Web Service Has Been Restarted.

else

        echo Certificate Renewal Failed! Additional steps have been skipped, and no changes have been made.

fi

 

Schedule Execution of Renew and Rekey Scripts

Each script can be scheduled in cron at your desired intervals. My personal preference is to renew every 2 months and to rekey once a year.

0 3 15 2,4,6,8,10,12 * ( /root/scripts/letsencrypt/renew-cert )
0 3 1 12 * ( /root/scripts/letsencrypt/rekey-cert )

Every other month on the 15th day at 03:00 renew-cert is run.

On Dec 1 at 03:00 rekey-cert is run.

NOTE: The delay between running rekey on the 1st and the subsequent renew on the 15th, is to allow for comfortable DNS propagation of the new DANE TLSA RRs. This is done prior to going live with the new certificate created with the new public key. If you do not require the DNS update portions of the scripts, then you could simply execute rekey-cert right before executing renew-cert on the 15th.

 

Additional Certbot Information

The certbot user guide can be found at: https://certbot.eff.org/docs/using.html#

Cerbot command-line options: https://certbot.eff.org/docs/using.html#certbot-command-line-options