Control Let's Encrypt 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 public/private 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 public/private key pair and certificate signing request for each renewal. This 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 unmodified CSR is used to request a renewed certificate.

This method faciliates the option to continue using the same CSR for ongoing certificate renewal requests. Then rekey the certificate signing request at your desired long-term interval by generating a new key pair and CSR just prior to the next renewal request.

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 frames, respective 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 then 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 pkey -in rsa-privkey.pem-out rsa-pubkey.pem -pubout

 

Next create a stripped down version of openssl.cnf based on the following template and save it as csr.cnf

[ 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
email.1 = This email address is being protected from spambots. You need JavaScript enabled to view it.
IP.1 = 198.51.100.1

In section [ req_distinguished_name ] change Common Name CN = example.com to the FQDN of your server.
In section [ alt_names ] add a DNS.1 = example.com statement with the FQDN of your server.

Optionally add any additional FQDN DNS entries that the server uses and you want included in the certificate. You can also add Internet IP address(es) and an email address to include in the certificate. Examples are provided in the template above. Only one DNS entry is required, the rest is optional based on your specific requirements.

 

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

Optionally to view and confirm the contents of the CSR in plain text.
openssl req -in rsa-csr.pem -text

 

Request a new certificate with the new CSR.
certbot certonly --standalone --csr rsa-csr.pem

 

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

To view the contents of a certificate in plain text.
openssl x509 -in 0000_cert.pem -text

 

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 are written in shells/bash making this a run-time dependency.
  • 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 --standalone authentication. Substitute this with your preferred authentication method.
  • When initially testing the scripts, insert the --dry-run directive into each of the certbot command lines. Remove the directive when everything executes error free, to begin receiving your live certificates.

The purpose of rekey-cert script is to generate a new public/private key pair and subsequent new certificate signing request, saving them in a rekey/ subdirectory. This script can be run at some extended interval of time such as once a year or longer.

The renew-cert script renews the certificate using the existing CSR. This script will be run every two months.
If however the rekey/ subdirectory exists, then the script first moves the new key pair and CSR files from rekey/ to the main script directory, and deletes the now empty rekey/ subdirectory. Then the script makes the usual renewal request using the new certificate signing request.

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

 

rekey-cert

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

# Create rekey/ directory if it does not exist, exit if it does exist.
if [ ! -d rekey/ ]; then
	mkdir rekey
	cd rekey/
else
	printf "\nThe rekey/ directory already exists!\n"
	printf "Either execute renew-cert next, or delete the rekey/ directory and rerun this script.\n\n"
	exit 1
fi

# Generate an RSA private key
printf "Generating New...\n"
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa-privkey.pem
printf "✔ Private Key\n"

# Generate the corresponding public key
openssl pkey -in rsa-privkey.pem -out rsa-pubkey.pem -pubout
printf "✔ Public Key\n"

# Generate the certificate signing request
openssl req -new -sha256 -key rsa-privkey.pem -nodes -config ../csr.cnf -outform pem -out rsa-csr.pem
printf "✔ Certificate Signing Request\n"

# Save the new TLSA hash
openssl req -noout -pubkey -in rsa-csr.pem | openssl rsa -pubin -outform DER | sha256 > tlsa311
printf "✔ TLSA Hash\n"

# Add the new TLSA 3 1 1 DNS resource records
NEWTLSA=`cat tlsa311`
printf "server zone-master.example.com\n" > nsupdate
printf "update add _443._tcp.example.com. 86400 IN TLSA 3 1 1 $NEWTLSA\n" >> nsupdate
printf "update add _443._tcp.www.example.com. 86400 IN TLSA 3 1 1 $NEWTLSA\n" >> nsupdate
printf "send\n" >> nsupdate
printf "update add _443._tcp.example.org. 86400 IN TLSA 3 1 1 $NEWTLSA\n" >> nsupdate
printf "update add _443._tcp.www.example.org. 86400 IN TLSA 3 1 1 $NEWTLSA\n" >> nsupdate
printf "send\n" >> nsupdate
printf "quit\n" >> nsupdate
nsupdate -k ../ddns-key nsupdate
rm -f nsupdate
printf "✔ TLSA Resource Record Added to DNS\n"

printf "\nAll Rekey Tasks Completed Successfully!\n\n"

 

renew-cert

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

if [ ! -f rsa-privkey.pem ] && [ ! -f rekey/rsa-privkey.pem ]; then

	# A private key doesn't exist yet therefore this is the first-run
	# Executing 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 the certificate with the existing CSR
	certbot certonly --standalone --csr rsa-csr.pem -n
else

	# If the rekey/ directory does exist then perform additional rekey related
	# tasks and renew the 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 Let's Encrypt Certificate With New CSR
	certbot certonly --standalone --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`
		printf "server zone-master.example.com\n" > nsupdate
		printf "update delete _443._tcp.example.com. 86400 IN TLSA 3 1 1 $OLDTLSA\n" >> nsupdate
		printf "update delete _443._tcp.www.example.com. 86400 IN TLSA 3 1 1 $OLDTLSA\n" >> nsupdate
		printf "send\n" >> nsupdate
		printf "update delete _443._tcp.example.org. 86400 IN TLSA 3 1 1 $OLDTLSA\n" >> nsupdate
		printf "update delete _443._tcp.www.example.org. 86400 IN TLSA 3 1 1 $OLDTLSA\n" >> nsupdate
		printf "send\n" >> nsupdate
		printf "quit\n" >> nsupdate
		nsupdate -k ddns-key nsupdate
		rm -f nsupdate
	fi
fi

if [ -f 0000_cert.pem ]; then
	if [ -f cert.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

	printf "Certificate Renewal Successful!\n\n"
else
	printf "Certificate Renewal Failed!\nAdditional steps have been skipped, and no changes have been made.\n\n"
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 0 27 11 * ( /root/scripts/letsencrypt/rekey-cert )
0 0 1 2,4,6,8,10,12 * ( /root/scripts/letsencrypt/renew-cert )

Every Nov 27 at midnight 00:00 rekey-cert is run.

On the first day of every other month (Feb Apr June Aug Oct Dec) at midnight 00:00 renew-cert is run.

 

Additional Certbot Information

The certbot user guide can be found at: https://eff-certbot.readthedocs.io/en/stable/using.html

Cerbot command-line options: https://eff-certbot.readthedocs.io/en/stable/using.html#certbot-command-line-options