DO NOT USE THE SCRIPT!!
After more testing, I discovered there are still some issues which need fixing before the script is safe for use. I will leave it here to serve as inspiration for other for the time being.
Hey everyone,
Inspired by the post on https://scotthelme.co.uk/hpkp-http-public-key-pinning/ I decided to write my own script for HTTP Public Key Pinning (at the leaf). When I finished the script I decided I might as well share it, since it might be usefull for others and perhaps people can provide feedback so I can improve the script further. So I am hereby releasing it under The Unlicense.
I developped this for my Debian Jessie server, but I think it should work with others as well.
Please give feedback and suggestions for improvements and if you want to use it, feel free!
Features:
- Checks whether the current certificate is about to expire and creates new keys, renews the certificate and updates the hpkp configuration - automatically!
- Works with cron! Set and forget!
- Uses a different key (and certificate signing request) for each certificate, reducing the damage from a private key compromise!
- Writes HPKP configuration to a seperate file, thereby not messing with other configuration files. The HPKP config can be included in the main config (like default-ssl.conf).
- Easy to use set-up variables to modify the script to your liking.
Set-up requirements:
- The backup pins need to be generated manually, preferably on another machine and saved on an offline medium like a cd.
- The hashes from the backup pins need to be calculated manually and be entered into the set-up variables of the script.
- The initial keys and CSRs have to be generated manually.
- The hpkp.conf file needs to be included in another apache config file to work.
[code]#!/bin/sh
=== BEGIN LICENSE ===
#This is free and unencumbered software released into the public domain.
#Anyone is free to copy, modify, publish, use, compile, sell, or
#distribute this software, either in source code form or as a compiled
#binary, for any purpose, commercial or non-commercial, and by any
#means.
#In jurisdictions that recognize copyright laws, the author or authors
#of this software dedicate any and all copyright interest in the
#software to the public domain. We make this dedication for the benefit
#of the public at large and to the detriment of our heirs and
#successors. We intend this dedication to be an overt act of
#relinquishment in perpetuity of all present and future rights to this
#software under copyright law.
#THE SOFTWARE IS PROVIDED âAS ISâ, WITHOUT WARRANTY OF ANY KIND,
#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
#OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
#ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
#OTHER DEALINGS IN THE SOFTWARE.
#For more information, please refer to http://unlicense.org
=== END LICENSE ===
Set-up variables
certdir=/home/[user]/scripts/cert # Cert directory
CSRdir=/home/[user]/scripts/cert # Path to directory containing the CSRs
keydir=/home/[user]/scripts/cert # Path to directory containing the keys
expire=2592000 # Renew certificate 30 days before expiration
backuppinhash1="â
backuppinhash2=â"
maxage=518400 # max-age of HPKP header in seconds (518400 = 60 days)
- url to report to
url="https://[some-hash].report-uri.io/r/default/hpkp/reportOnly"
header=âHeader always set Public-Key-Pins-Report-Only\â # Report only mode
#header=âHeader always set Public-Key-Pins\â # uncomment for enforce mode
Check whether the cert will expire
if ! /usr/bin/openssl x509 -checkend $expire -noout -in $certdir/cert.pem
then # It will
/bin/echo Cert due for renewal
Remove current CSR and key
/bin/rm -f $CSRdir/[domain].1.csr
/bin/rm -f $keydir/[domain].1.key
Shift other CSRs and keys by 1
/bin/mv $CSRdir/[domain].2.csr $CSRdir/[domain].1.csr
/bin/mv $keydir/[domain].2.key $keydir/[domain].1.key
/bin/mv $CSRdir/[domain].3.csr $CSRdir/[domain].2.csr
/bin/mv $keydir/[domain].3.key $keydir/[domain].2.key
Create new key and CSR
/usr/bin/openssl genrsa 4096 > $keydir/[domain].3.key
/usr/bin/openssl req -new -key $keydir/[domain].3.key -nodes -sha512
-subj â/CN=[domain]â -reqexts SAN
-out $CSRdir/[domain].3.csr -config $CSRdir/csr.conf
Rename current cert and chains before renewal
/bin/mv $certdir/cert.pem $certdir/cert.pem.bak
/bin/mv $certdir/chain.pem $certdir/chain.pem.bak
/bin/mv $certdir/fullchain.pem $certdir/fullchain.pem.bak
Sign the CSR with Letsencrypt using certbot
/usr/bin/certbot certonly -nvv --non-interactive --must-staple
âcsr $CSRdir/[domain].1.csr
âwebroot -w /var/www/html
-d [domain] -d [domain2]
âcert-path $certdir/cert.pem
âchain-path $certdir/chain.pem
âfullchain-path $certdir/fullchain.pem
Renewal succeeded, so safe to remove backups
/bin/rm -f $certdir/cert.pem.bak
/bin/rm -f $certdir/chain.pem.bak
/bin/rm -f $certdir/fullchain.pem.bak
Set ownership and permission (just to be sure)
/bin/chown root:root $certdir/cert.pem
/bin/chown root:root $certdir/chain.pem
/bin/chown root:root $certdir/fullchain.pem
/bin/chown root:root $keydir/[domain].1.key
/bin/chown root:root $CSRdir/[domain].1.csr
/bin/chown root:root $keydir/[domain].2.key
/bin/chown root:root $CSRdir/[domain].2.csr
/bin/chown root:root $keydir/[domain].3.key
/bin/chown root:root $CSRdir/[domain].3.csr
/bin/chmod 600 $certdir/cert.pem
/bin/chmod 600 $certdir/chain.pem
/bin/chmod 600 $certdir/fullchain.pem
/bin/chmod 600 $keydir/[domain].1.key
/bin/chmod 600 $CSRdir/[domain].1.csr
/bin/chmod 600 $keydir/[domain].2.key
/bin/chmod 600 $CSRdir/[domain].2.csr
/bin/chmod 600 $keydir/[domain].3.key
/bin/chmod 600 $CSRdir/[domain].3.csr
Calculate HPKP hashes
hash1=$(/usr/bin/openssl req -pubkey
< $CSRdir/[domain].1.csr
| /usr/bin/openssl pkey -pubin -outform der
| /usr/bin/openssl dgst -sha256 -binary | /usr/bin/base64)
hash2=$(/usr/bin/openssl req -pubkey
< $CSRdir/[domain].2.csr
| /usr/bin/openssl pkey -pubin -outform der
| /usr/bin/openssl dgst -sha256 -binary | /usr/bin/base64)
hash3=$(/usr/bin/openssl req -pubkey
< $CSRdir/[domain].3.csr
| /usr/bin/openssl pkey -pubin -outform der
| /usr/bin/openssl dgst -sha256 -binary | /usr/bin/base64)
Write hashes to Apache2 configuration
/bin/echo $header >> $certdir/hpkp.conf
/bin/echo -e " âpin-sha256="$hash1";" >> $certdir/hpkp.conf
/bin/echo -e âpin-sha256=â$hash2";" >> $certdir/hpkp.conf
/bin/echo -e âpin-sha256=â$hash3";" >> $certdir/hpkp.conf
/bin/echo -e âpin-sha256=â$backuppinhash1";" >> $certdir/hpkp.conf
/bin/echo -e âpin-sha256=â$backuppinhash2";" >> $certdir/hpkp.conf
/bin/echo -e âmax-age=â$maxage";" >> $certdir/hpkp.conf
/bin/echo -e âreport-uri=â$url"â" >> $certdir/hpkp.conf
/bin/mv $certdir/hpkp.conf /etc/apache2/sites-available/
Reload the services
/usr/sbin/service apache2 reload
/usr/sbin/service cups reload
/usr/sbin/service dovecot reload
/usr/sbin/service proftpd reload
/usr/sbin/service postfix reload
else
/bin/echo Cert not yet due for renewal
fi[/code]
csr.conf:
[req]
distinguished_name = dn
[dn]
[SAN]
subjectAltName=DNS:[domain1],DNS:[domain2]
EDIT: Fixed mistake in the script (backup pins were not included)