Skip to content

Instantly share code, notes, and snippets.

@killerbees19
Last active June 12, 2024 17:25
Show Gist options
  • Save killerbees19/2f3595eed8ce7b26e9a445b712bd09d4 to your computer and use it in GitHub Desktop.
Save killerbees19/2f3595eed8ce7b26e9a445b712bd09d4 to your computer and use it in GitHub Desktop.
[certbot] Let's Encrypt: Dual RSA/ECDSA certificates without frequent key changes

Tested at

  • Debian Buster [2019-07-18]
  • Debian Bullseye [2022-04-28]

Installation

apt update
apt full-upgrade

apt install certbot nginx-full
systemctl disable certbot.timer

mkdir /etc/ssl/certbot
chmod 0700 /etc/ssl/certbot

#certbot register -m "postmaster@$(hostname -f)" --agree-tos --non-interactive --staging
certbot register -m "postmaster@$(hostname -f)" --agree-tos --non-interactive

openssl dhparam -out /etc/nginx/dhparam-4096.pem 4096
~/path/to/certbot.sh && service nginx restart

(100% A+ Rating at SSLLabs, but only 90% at TLS 1.3 ciphers!)

#!/bin/bash
###
# https://gist.github.com/killerbees19/2f3595eed8ce7b26e9a445b712bd09d4
###
set -euf -o pipefail
CN=$(hostname -f)
SAN="$CN,www.$CN"
DATE=$(date +%Y%m%d-%H%M%S)
DIR="/etc/ssl/certbot"
WEB="/var/www/html"
ARGS= # --staging
RENEWAL=31
LATEST_KEY=".key"
LATEST_CSR=".csr"
LATEST_CERT=".pem"
LATEST_CHAIN=".chain.pem"
LATEST_FULLCHAIN=".fullchain.pem"
CURRENT_KEY=".$DATE.key"
CURRENT_CSR=".$DATE.csr"
CURRENT_CERT=".$DATE.pem"
CURRENT_CHAIN=".$DATE.chain.pem"
CURRENT_FULLCHAIN=".$DATE.fullchain.pem"
NEXT_KEY=".next.key"
NEXT_CSR=".next.csr"
# TODO: Check if CN/SAN changed? (csr != crt)
# ...
# TODO: Cleanup old files?
# ...
if [ ! -d "$DIR" ]
then
echo "Error: $DIR not found!" 1>&2
exit 1
fi
generate_csr()
{
OLDIFS="$IFS"
IFS=","; san=
for dns in $SAN
do
if [[ "$san" != "" ]]
then
san+=", "
fi
san+="DNS:$dns"
done; IFS="$OLDIFS"
if [[ "$san" != "" ]]
then
san="subjectAltName = \"$san\""
fi
openssl genrsa -out "$DIR/rsa$CURRENT_KEY" 4096 2>/dev/null
openssl req -new -sha256 -key "$DIR/rsa$CURRENT_KEY" -out "$DIR/rsa$CURRENT_CSR" -subj "/CN=$CN" -addext "$san"
openssl ecparam -genkey -name secp384r1 | openssl ec -out "$DIR/ecc$CURRENT_KEY" 2>/dev/null
openssl req -new -sha256 -key "$DIR/ecc$CURRENT_KEY" -out "$DIR/ecc$CURRENT_CSR" -subj "/CN=$CN" -addext "$san"
ln -s -f "./rsa$CURRENT_KEY" "$DIR/rsa$NEXT_KEY"
ln -s -f "./rsa$CURRENT_CSR" "$DIR/rsa$NEXT_CSR"
ln -s -f "./ecc$CURRENT_KEY" "$DIR/ecc$NEXT_KEY"
ln -s -f "./ecc$CURRENT_CSR" "$DIR/ecc$NEXT_CSR"
echo "Notice: New key/csr pair generated." 1>&2
echo "It'll be used for new certificates." 1>&2
}
generate_crt()
{
if [ ! -e "$DIR/rsa$NEXT_KEY" ] || \
[ ! -e "$DIR/rsa$NEXT_CSR" ] || \
[ ! -e "$DIR/ecc$NEXT_KEY" ] || \
[ ! -e "$DIR/ecc$NEXT_CSR" ]
then
generate_csr
fi
mode="${1:-}"
[[ "$mode" == "quiet" ]] \
&& quiet=1 || quiet=0
if [ "$(basename "$(readlink -f "$DIR/rsa$NEXT_KEY")")" != "$(basename "$(readlink -f "$DIR/rsa$LATEST_KEY")")" ] || \
[ "$(basename "$(readlink -f "$DIR/rsa$NEXT_CSR")")" != "$(basename "$(readlink -f "$DIR/rsa$LATEST_CSR")")" ] || \
[ "$(basename "$(readlink -f "$DIR/ecc$NEXT_KEY")")" != "$(basename "$(readlink -f "$DIR/ecc$LATEST_KEY")")" ] || \
[ "$(basename "$(readlink -f "$DIR/ecc$NEXT_CSR")")" != "$(basename "$(readlink -f "$DIR/ecc$LATEST_CSR")")" ]
then
mode="force"
fi
if [ "$mode" == "force" ]; then echo "Notice: Renewal forced..." 1>&2
elif [ ! -e "$DIR/rsa$LATEST_CERT" ] || [ ! -e "$DIR/ecc$LATEST_CERT" ] || \
! test "$(find "$DIR/rsa$LATEST_CERT" "$DIR/ecc$LATEST_CERT" -mtime +"$RENEWAL")"
then
if [ "$quiet" -ne 1 ]
then
echo "Warning: Renewal not required yet!" 1>&2
echo " Use \"gencrt\" to force it." 1>&2
fi
exit 0
fi
# shellcheck disable=SC2086
certbot certonly $ARGS \
--non-interactive --quiet \
--csr "$DIR/rsa$NEXT_CSR" \
--cert-path "$DIR/rsa$CURRENT_CERT" \
--chain-path "$DIR/rsa$CURRENT_CHAIN" \
--fullchain-path "$DIR/rsa$CURRENT_FULLCHAIN" \
--webroot --webroot-path "$WEB" --domain "$SAN"
# shellcheck disable=SC2086
certbot certonly $ARGS \
--non-interactive --quiet \
--csr "$DIR/ecc$NEXT_CSR" \
--cert-path "$DIR/ecc$CURRENT_CERT" \
--chain-path "$DIR/ecc$CURRENT_CHAIN" \
--fullchain-path "$DIR/ecc$CURRENT_FULLCHAIN" \
--webroot --webroot-path "$WEB" --domain "$SAN"
ln -s -f "./rsa$CURRENT_CERT" "$DIR/rsa$LATEST_CERT"
ln -s -f "./rsa$CURRENT_CHAIN" "$DIR/rsa$LATEST_CHAIN"
ln -s -f "./rsa$CURRENT_FULLCHAIN" "$DIR/rsa$LATEST_FULLCHAIN"
ln -s -f "./$(basename "$(readlink -f "$DIR/rsa$NEXT_KEY")")" "$DIR/rsa$LATEST_KEY"
ln -s -f "./$(basename "$(readlink -f "$DIR/rsa$NEXT_CSR")")" "$DIR/rsa$LATEST_CSR"
ln -s -f "./ecc$CURRENT_CERT" "$DIR/ecc$LATEST_CERT"
ln -s -f "./ecc$CURRENT_CHAIN" "$DIR/ecc$LATEST_CHAIN"
ln -s -f "./ecc$CURRENT_FULLCHAIN" "$DIR/ecc$LATEST_FULLCHAIN"
ln -s -f "./$(basename "$(readlink -f "$DIR/ecc$NEXT_KEY")")" "$DIR/ecc$LATEST_KEY"
ln -s -f "./$(basename "$(readlink -f "$DIR/ecc$NEXT_CSR")")" "$DIR/ecc$LATEST_CSR"
if [ "$quiet" -ne 1 ]
then
echo "Notice: New certificate saved." 1>&2
echo "Please restart your daemons." 1>&2
fi
}
case "${1:-}" in
"genkey"|"gencsr")
generate_csr
;;
"gencrt"|"genpem")
generate_crt "force"
;;
"cron")
generate_crt "quiet"
;;
"help")
echo "Usage: $0 [{genkey|gencsr|gencrt|genpem|cron|help}]"
exit 0
;;
*)
generate_crt
;;
esac
@weekly ~/path/to/certbot.sh cron && /usr/sbin/service nginx restart
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
ssl_stapling on;
ssl_stapling_verify on;
ssl_certificate /etc/ssl/certbot/rsa.fullchain.pem;
ssl_certificate_key /etc/ssl/certbot/rsa.key;
ssl_certificate /etc/ssl/certbot/ecc.fullchain.pem;
ssl_certificate_key /etc/ssl/certbot/ecc.key;
add_header Strict-Transport-Security "max-age=15552000";
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384";
ssl_dhparam /etc/nginx/dhparam-4096.pem;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_session_timeout 1d;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment