Last active
April 22, 2020 06:59
-
-
Save AliceWonderMiscreations/de1a37b41df545eba3b6d6e77f6f29fb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# RSA 3072-bit | |
# Tested - works for me. Line number notes assume first line is line 1 | |
# Modify lines 85-88, 90 for your own identity (leave ${FQDN} line alone) | |
# Modify line 19 for your openssl/libressl binary path | |
# Modify line 20 for your certbot path | |
# Example Usage: | |
# | |
# sudo sh letsencrypt.sh example.org www.example.org support.example.org | |
# (all arguements need DNS records pointing to server running on) | |
# | |
# Stop web server daemon before running this script. | |
OPENSSL="/usr/bin/libressl" | |
CERTBOT="/usr/bin/certbot" | |
if [ ! -x ${OPENSSL} ]; then | |
echo "Please edit script and define your OpenSSL API implementation (line 19)." | |
exit 1 | |
fi | |
[ "$(id -u)" != "0" ] && exit 1 | |
FQDN="$1" | |
DATE="`date +%Y%m%d`" | |
CSR="${FQDN}-EFFLE-${DATE}.csr" | |
CFG="${FQDN}-EFFLE.cfg" | |
X509="${FQDN}-EFFLE-${DATE}.crt" | |
CAB="${FQDN}-EFFLE-cab-${DATE}.crt" | |
umask 0277 | |
[ ! -d /etc/pki/tls/eff_private ] && mkdir -p /etc/pki/tls/eff_private | |
pushd /etc/pki/tls/eff_private > /dev/null 2>&1 | |
# if existing key is less than 320 days old, use it. Otherwise generate a fresh | |
NEWKEY=0 | |
keycount=`find . -type f -print |grep "^\./${FQDN}-" |wc -l` | |
if [ $keycount -eq 0 ]; then | |
NEWKEY=1 | |
else | |
LATEST=`find . -type f -print |grep "^\./${FQDN}-" |tail -1 |sed -e s?"^\./"?""?` | |
AGE=`echo $(($(date +%s) - $(date +%s -r ${LATEST})))` | |
let "DAYS = ${AGE} / 86400" | |
if [ ${DAYS} -ge 320 ]; then | |
NEWKEY=1 | |
else | |
PVT="${LATEST}" | |
fi | |
fi | |
if [ ${NEWKEY} -eq 1 ]; then | |
PVT="${FQDN}-EFFLE-${DATE}.key" | |
${OPENSSL} genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out "${PVT}" | |
fi | |
if [ ! -f "${PVT}" ]; then | |
echo "Something went wrong, no suitable private key" | |
exit 1 | |
fi | |
umask 0022 | |
popd > /dev/null 2>&1 | |
# generate CSR | |
[ ! -d /etc/pki/tls/csr ] && mkdir /etc/pki/tls/csr | |
pushd /etc/pki/tls/csr > /dev/null 2>&1 | |
[ -f "${CFG}" ] && rm -f "${CFG}" | |
[ -f "${CSR}" ] && rm -f "${CSR}" | |
cat <<EOF > "${CFG}" | |
[req] | |
distinguished_name = req_distinguished_name | |
req_extensions = ext | |
prompt = no | |
[ req_distinguished_name ] | |
C = YourCountryCode | |
ST = YourState | |
L = YourCity | |
O = Your Organization Name | |
CN = ${FQDN} | |
emailAddress = [email protected] | |
[ext] | |
basicConstraints = critical,CA:FALSE | |
keyUsage = critical,digitalSignature | |
extendedKeyUsage = serverAuth,clientAuth | |
subjectAltName = @san | |
[san] | |
EOF | |
COUNTER=0 | |
for arg in $@; do | |
((COUNTER++)) | |
echo "DNS.${COUNTER} = ${arg}" >> "${CFG}" | |
done | |
${OPENSSL} req -new -key "../eff_private/${PVT}" -out "${CSR}" -config "${CFG}" | |
if [ $? -ne 0 ]; then | |
echo "Problem creating CSR" | |
exit 1 | |
fi | |
popd > /dev/null 2>&1 | |
if [ ${NEWKEY} -eq 1 ]; then | |
echo "New Private Key Generated: /etc/pki/tls/eff_private/${PVT}" | |
fi | |
echo "CSR file: /etc/pki/tls/csr/${CSR}" | |
if [ -x ${CERTBOT} ]; then | |
[ ! -d /etc/pki/tls/eff_certs ] && mkdir -p /etc/pki/tls/eff_certs | |
${CERTBOT} certonly --standalone --csr /etc/pki/tls/csr/${CSR} \ | |
--cert-path /etc/pki/tls/eff_certs/${X509} \ | |
--chain-path /etc/pki/tls/eff_certs/${CAB} | |
fi | |
if [ -f "/etc/pki/tls/eff_certs/${X509}" ]; then | |
pushd /etc/pki/tls/eff_certs/ | |
# generate DANE | |
FINGERPRINT="`${OPENSSL} x509 -noout -fingerprint -sha256 < "${X509}" |tr -d : |cut -d"=" -f2`" | |
echo "" | |
echo "TLSA from Cert:" | |
echo "3 0 1 ${FINGERPRINT}" | |
echo "" | |
echo "TLSA from PubKey:" | |
FINGERPRINT="`${OPENSSL} x509 -in ${X509} -noout -pubkey \ | |
|${OPENSSL} pkey -pubin -outform DER \ | |
|${OPENSSL} dgst -sha256 -binary \ | |
|hexdump -ve '/1 "%02x"'`" | |
FINGERPRINT=${FINGERPRINT^^} | |
echo "3 1 1 ${FINGERPRINT}" | |
fi | |
exit 0 |
I like your approach of keeping Apache/key parameters configuration separated from letsencrypt process. I've adapted your script as a cronjob script. User does not need to think.
Feel free to use the version in PS/part of it or just keep it here for the others.
Thank you, a nice idea!
Brani
PS:
#!/bin/bash
#
# Source: https://gist.github.com/AliceWonderMiscreations/de1a37b41df545eba3b6d6e77f6f29fb
# See thread: https://community.letsencrypt.org/t/use-existing-private-key/60182
#
# April/2018 Alice Wonder - Original script
# April/2020 Branislav Siarsky - Adapted as a cronjob script, used/tested on Debian 10 buster
#
# This script is written as a cronjob script. Run it once a day from a crontab.
# It verifies if key and certificate fulfil differently configurable expiration dates.
# If key/certificate is not expired nothing is done.
# If the certificate or key needs to be regenerated or they do not exist yet:
# 1. This script generates key (if expires/not existing) and certificate request
# 2. Stops apache
# 3. Calls letsencrypt
# 4. If a new certificate was properly generated
# Script makes backup of old key/certificate (if exists)
# Script installs new key and certificate
# 5. If a new certificate was not generated (due to network problems etc.)
# Script deletes own temporary files
# Existing key and certificate stay untouched in place
# 6. Starts apache
#
# Example usage. Call as root:
# letsencrypt.sh example.org www.example.org support.example.org
#
# All DNS domains (example.org www.example.org support.example.org) need a DNS record pointing to the server where this script is running on.
# The first domain (example.org) is used as the primary (FQDN), key and certificate files will get FQDN name (example.org.pem, example.org.key).
# Do not forget to adapt your apache SSL configuration.
#
# The other domains (www.example.org support.example.org) are added into the same certificate into DNS certificate request section.
#
# If you need to reinforce certificate generation (you want the script to ignore configured expiration parameters), use "--force" option.
# Be aware of letsencrypt limits, if you use "--force" too often you will be blocked for some time.
# Use letsencrypt test system if you need to test adaptions of this script.
#
# What you might want to change (search for a section with this label in this script):
# - System/script parameters
# - Private key parameters
#
# What you definitely want to change
# - Certificate parameters
#
print_cert_hashes()
{
if [ "${PRINT_CERT_HASHES}" == "1" ]; then
if [ -f "${CERT_DIR}/${X509}" ]; then
# generate DANE
FINGERPRINT="`${OPENSSL} x509 -noout -fingerprint -sha256 < "${CERT_DIR}/${X509}" | tr -d : | cut -d"=" -f2`"
echo ""
echo "TLSA from Cert:"
echo "3 0 1 ${FINGERPRINT}"
echo "TLSA from PubKey:"
FINGERPRINT="`${OPENSSL} x509 -in ${X509} -noout -pubkey \
| ${OPENSSL} pkey -pubin -outform DER \
| ${OPENSSL} dgst -sha256 -binary \
| hexdump -ve '/1 "%02x"'`"
FINGERPRINT=${FINGERPRINT^^}
echo "3 1 1 ${FINGERPRINT}"
fi
fi
}
#delete all temporary files
delete_temp_files()
{
FILE="${CSR_DIR}/${CFG}"
rm ${FILE} >/dev/null 2>&1
FILE="${CSR_DIR}/${CSR}"
rm ${FILE} >/dev/null 2>&1
FILE="${PK_DIR}/${PK}"
rm ${FILE} >/dev/null 2>&1
FILE="${CERT_DIR}/${X509}"
rm ${FILE} >/dev/null 2>&1
FILE="${CERT_DIR}/${CAB}"
rm ${FILE} >/dev/null 2>&1
}
#System/script parameters
OPENSSL="/usr/bin/openssl"
CERTBOT="/usr/bin/certbot"
PK_DIR="/etc/ssl/private"
CSR_DIR="/etc/ssl/certs"
CERT_DIR="/etc/ssl/certs"
APACHE_INITD="/etc/init.d/apache2"
#Private key parameters
PK_OWNER=":ssl-cert" #set owner of private key, if needed
PK_UMASK="0640" #set umask of private key, if needed
PK_TYPE="RSA" #generate a RSA key type
PK_SIZE="3072" #generate a key of this size
PK_MAX_AGE="320" #generate new private key if it is elder than X days
CERT_RENEW_BEFORE="5" #renew certificate X days before expires
PRINT_CERT_HASHES="0" #set to 1 if you use DNSSEC or DANE and needs certificate hashes
SCRIPT_NAME=`basename "$0"`
if [ "$(id -u)" != "0" ]; then
echo "Start ${SCRIPT_NAME} with root privileges"
exit 1
fi
FORCE_RUN=0
if [ "$1" == "--force" ]; then
FORCE_RUN=1
shift
fi
if [ "$1" == "" ]; then
echo "Usage: ${SCRIPT_NAME} {--force} domain1 domain2 ... domainX"
echo " --force : ignore age of certificate and private key and force calling letsencrypt"
echo "Example: ${SCRIPT_NAME} example.com www.example.com monitor.example.com"
exit 1
fi
if [ ! -x ${OPENSSL} ]; then
echo "OpenSSL command not set"
exit 1
fi
if [ ! -d ${PK_DIR} ]; then
echo "Private key directory ${PK_DIR} does not exist"
exit 1
fi
if [ ! -d ${CSR_DIR} ]; then
echo "CSR directory ${CSR_DIR} does not exist"
exit 1
fi
if [ ! -d ${CERT_DIR} ]; then
echo "Certificates directory ${CERT_DIR} does not exist"
exit 1
fi
FQDN="$1"
DATE="`date +%Y%m%d`"
#Files generated by this script
DATE="`date +%Y%m%d`"
CFG="${FQDN}.cfg.${DATE}" #template for certificate request (contains data like CN)
PK="${FQDN}.key.${DATE}" #private key
CSR="${FQDN}.csr.${DATE}" #certificate request
#Files generated by certbot
X509="${FQDN}.pem.${DATE}" #signed certificate
CAB="${FQDN}.cab.${DATE}" #chain path
# if existing key is less than ${PK_MAX_AGE} days old, use it. Otherwise generate a new one
NEED_NEWPK=1
if [ $FORCE_RUN -eq 0 ]; then
COUNT=`cd ${PK_DIR}; find . -type f -name "${FQDN}.key" | wc -l`
if [ $COUNT -gt 0 ]; then
PK_LAST=`cd ${PK_DIR}; find . -type f -name "${FQDN}.key" | xargs basename`
PK_LAST_AGE=`echo $(($(date +%s) - $(date +%s -r ${PK_DIR}/${PK_LAST})))`
let "SECONDS = ${PK_MAX_AGE} * 86400"
if [ ${PK_LAST_AGE} -le ${SECONDS} ]; then
#reuse old private key
cp -p "${PK_DIR}/${PK_LAST}" "${PK_DIR}/${PK}"
NEED_NEWPK=0
fi
fi
fi
# check if certificate exists and if so, if it expires in ${CERT_RENEW_BEFORE} days
NEED_NEWCSR=1
if [ $FORCE_RUN -eq 0 ]; then
FILE_DATUM="${CERT_DIR}/${X509}"
FILE="${FILE_DATUM%.*}"
if [ -f "${FILE}" ]; then
let "SECONDS = ${CERT_RENEW_BEFORE} * 86400"
${OPENSSL} x509 -checkend ${SECONDS} -noout -in "${FILE}" >/dev/null 2>&1
if [ $? -eq 0 ]; then
#Certificate is not going to expire in next ${CERT_RENEW_BEFORE} days, no renewal needed
NEED_NEWCSR=0
fi
fi
fi
if [ "${NEED_NEWCSR}" == "0" -a "${NEED_NEWPK}" == "0" ]; then
echo "${FQDN}: Certificate and private key are up to date, nothing to do"
delete_temp_files
print_cert_hashes
exit 1
fi
if [ ${NEED_NEWPK} -eq 1 ]; then
echo "Generating new key ${PK}"
${OPENSSL} genpkey -algorithm ${PK_TYPE} -pkeyopt rsa_keygen_bits:${PK_SIZE} -out "${PK_DIR}/${PK}"
if [ "${PK_OWNER}" != "" ]; then
chown "${PK_OWNER}" "${PK_DIR}/${PK}"
fi
if [ "${PK_UMASK}" != "" ]; then
chmod "${PK_UMASK}" "${PK_DIR}/${PK}"
fi
else
echo "Using existing key ${PK_DIR}/${PK}"
fi
if [ ! -f "${PK_DIR}/${PK}" ]; then
echo "Something went wrong, no suitable private key"
exit 1
fi
# generate CSR
rm -f ${CSR_DIR}/${CFG} >/dev/null 2>&1
rm -f ${CSR_DIR}/${CSR} >/dev/null 2>&1
#Certificate parameters
cat <<EOF > "${CSR_DIR}/${CFG}"
[req]
distinguished_name = req_distinguished_name
req_extensions = ext
prompt = no
[ req_distinguished_name ]
C = CH
ST = Basel Stadt
L = Basel
O = Smartdone GmbH
CN = ${FQDN}
emailAddress = hostmaster@${FQDN}
[ext]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = serverAuth,clientAuth
subjectAltName = @san
[san]
EOF
COUNTER=0
for arg in $@; do
((COUNTER++))
echo "DNS.${COUNTER} = ${arg}" >> "${CSR_DIR}/${CFG}"
done
echo "Generating CSR"
${OPENSSL} req -new -key "${PK_DIR}/${PK}" -out "${CSR_DIR}/${CSR}" -config "${CSR_DIR}/${CFG}"
if [ $? -ne 0 ]; then
echo "Problem creating CSR"
delete_temp_files
exit 1
else
echo "CSR file ${CSR_DIR}/${CSR} generated"
fi
if [ -x ${CERTBOT} ]; then
echo "Stopping Apache"
${APACHE_INITD} stop
${CERTBOT} certonly --standalone --csr ${CSR_DIR}/${CSR} \
--cert-path ${CERT_DIR}/${X509} \
--chain-path ${CERT_DIR}/${CAB}
RC="$?"
if [ ${RC} -eq 0 -a -f ${CERT_DIR}/${X509} -a -f ${CERT_DIR}/${CAB} ]; then
echo "CSR signing OK"
FILE_DATUM="${PK_DIR}/${PK}"
FILE="${FILE_DATUM%.*}"
echo "Backuping ${FILE}"
mv ${FILE} ${FILE}.bak >/dev/null 2>&1
mv ${FILE_DATUM} ${FILE} >/dev/null 2>&1
FILE_DATUM="${CERT_DIR}/${X509}"
FILE="${FILE_DATUM%.*}"
echo "Backuping ${FILE}"
mv ${FILE} ${FILE}.bak >/dev/null 2>&1
mv ${FILE_DATUM} ${FILE} >/dev/null 2>&1
FILE_DATUM="${CERT_DIR}/${CAB}"
FILE="${FILE_DATUM%.*}"
echo "Backuping ${FILE}"
mv ${FILE} ${FILE}.bak >/dev/null 2>&1
mv ${FILE_DATUM} ${FILE} >/dev/null 2>&1
else
echo "Problem with signing of CSR"
if [ ${RC} -ne 0 ]; then
echo " ${CERTBOT} return code ${RC}"
fi
FILE="${CERT_DIR}/${X509}"
if [ ! -f ${FILE} ]; then
echo " ${FILE} not created"
fi
FILE="${CERT_DIR}/${CAB}"
if [ ! -f ${FILE} ]; then
echo " ${FILE} not created"
fi
fi
${APACHE_INITD} start
fi
delete_temp_files
print_cert_hashes
exit 0
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
subjectAltName is properly working.