HPKP Support tool

Hi all,

After reading quite a lot of documentation on HPKP, I decided to enable it on my site which is using an LE-issued certificate via certbot.

Let me preface this by saying that I also found today that Comodo offers a free 90 day certificate, so I have opted to go with them for my first backup. I am still working on sourcing a secondary backup.

Also, I want to note that the site where I have deployed HPKP is merely a test site for things I plan to do at work.

In the process of setting this up, I wanted to make it automated so that the pins are updated when LetsEncrypt renews.

Since I am using LE for my primary certificate, and have opted to go with Comodo for my secondary, I have downloaded both LE root CA files, and the Comodo intermediate CA bundle (I could not find their root CA online so if anyone has a link, that would be appreciated!)

I went ahead and generated pins against my existing LE cert, as well as the 2 LE root CAs and the Comodo intermediate CA. After manually inserting those into my configuration, I started working on a script which can be called with --renew-hook in either manual or automated renewals.

THIS WILL NOT WORK WITHOUT THE INITIAL SETUP OF CONFIGURING YOUR SERVER WITH HPKP – IT IS NOT AN ALL-IN-ONE SCRIPT, INITIAL SETUP IS MANUAL

Disclaimer: I am not responsible if you brick your website with this script. You have been warned. Set the maximum age to a very low number, or use the HPKP specific report-only header rather than the enforcing header.

To run this tool, call it as part of your renewal:

certbot-auto renew --renew-hook “/path/to/script.sh”

You must setup HPKP on your own first before using this script.

Once you have HPKP setup, this tool will simply update the pins in your apache configuration when a renewal takes place.

I am open to suggestions, but this is a free-time-only project, and so I will only be providing limited support. Also, I only have CentOS and I am not familiar with other distros, so if you want to port it, or add features, please feel free.

Also, the top of the file contains a small configuration section for users to outline their apache configuration. This is the only part of the script which should be edited by normal users. Please do make necessary edits, as my defaults do not apply on any system except for my own.

#!/bin/bash
#
# letsencrypt-hpkp-post-renewal-hook.sh
#
# Author: Thomas D. Spear <speeddymon@gmail.com>
# Version: 0.1
# Date: 2018-05-05
# Description: Renews your certs using certbot-auto and then generates new HPKP PINs and inserts them into apache config

# Count how many certs are being renewed
shopt -s nullglob
CERTPATHS=(/etc/letsencrypt/archive/*)
DIRECTORIES=(/var/www/vhosts/*)
shopt -u nullglob

# Change this to your own conf file path
CONFPATH=/etc/httpd/conf.d/vhosts
DEFAULTCONF=1-default
DEFAULTDOMAIN=$(hostname -f)

########## End of user configurable options ##########

# Exit if we can't find directories
[ ${#CERTPATHS[@]} -lt 1 ] && exit 1
[ ${#DIRECTORIES[@]} -lt 1 ] && exit 1

# We will use this later for domains renewed as Subject Alternative Names on another certificate
PASS2=("${DIRECTORIES[@]##*/}")

for CERTDIR in "${CERTPATHS[@]}"
do
    # Path for the cert directory we are working on
    if [ ! -d "$CERTDIR" ]
    then
        echo "Cannot find directory $CERTDIR. Please check renewal manually"
        echo "Please note: HPKP will not work for this site and you may have downtime if this is not investigated soon!"
        continue
    fi

    # Path to the cert
    CERT=$(find "$CERTDIR" -mtime 0 -type f -name "cert*.pem")

    declare -p CERT 2>/dev/null | grep -q -- '^declare -a'
    # In case we got an array from the find above, we keep only the last element
    if [ $? -eq 0 ]
    then
        # In case this is an array, let's only keep the last element
        CERT=${CERT[-1]}
    fi

    # In case we got multiple paths from the find above, we remove all but the last
    CERT=${CERT//*[$'\t\r\n ']}

    # Skip if we can't find the cert
    if [ ! -e "$CERT" ]
    then
        echo "Cannot find cert $CERT. Please check renewal manually."
        echo "Please note: HPKP will not work for this site and you may have downtime if this is not investigated soon!"
        continue
    fi

    # Domain we are working on
    DOMAIN=${CERTDIR##*/}

    # Path to the config
    CONF=$CONFPATH/$DOMAIN-le-ssl.conf
    if [ ! -e "$CONF" ]
    then
        if [ "$DOMAIN" != "$DEFAULTDOMAIN" ]
        then
            echo "Cannot find config $CONF. Please check renewal manually."
            echo "Please note: HPKP will not work for this site and you may have downtime if this is not investigated soon!"
            continue
        fi
        CONF=$CONFPATH/$DEFAULTCONF-le-ssl.conf
    fi

    # New PIN from the new cert
    PIN=$(openssl x509 -pubkey < "$CERT" | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64)

    # Search and replace the old primary PIN with the new one
    sed -i "s|\\(pin-sha256=\\\\\"\\)[A-Za-z0-9+/=]\\+|\\1$PIN|1" $CONF
    echo "New PIN ($PIN) written to $CONF"

    # Cleanup old saves
    rm -f /root/hpkp/"$DOMAIN"/primary/*

    find $CERTDIR -mtime 0 -type f -exec cp '{}' /root/hpkp/$DOMAIN/primary \;
    PASS2=("${PASS2[@]##$DOMAIN}")
done

for DOMAIN in ${PASS2[@]}
do
    # Default is refreshed as part of the for loop above
    if [ "$DOMAIN" == "default" ]
    then
        continue
    fi

    CONF=$(find $CONFPATH -type f -name "$DOMAIN-le-ssl.conf")
    if [ ! -e "$CONF" ]
    then
        echo "Cannot find config $CONF. Please check renewal manually."
        echo "Please note: HPKP will not work for this site and you may have downtime if this is not investigated soon!"
        continue
    fi

    CERT=$(awk '/SSLCertificateFile/ {print $NF}' "$CONFPATH/$DOMAIN-le-ssl.conf")

    # New PIN from the new cert
    PIN=$(openssl x509 -pubkey < "$CERT" | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64)

    # Search and replace the old primary PIN with the new one
    sed -i "s|\\(pin-sha256=\\\\\"\\)[A-Za-z0-9+/=]\\+|\\1$PIN|1" "$CONF"
    echo "New PIN ($PIN) written to $CONF"
done

exit 0
1 Like

Show their intermediate cert and I can get it from that.

I'm glad to see some progress in the automation of this tedious process.

The Comodo root cert you need might be:
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
NVOFBkpdn627G190
-----END CERTIFICATE-----

OR MAYBE:

-----BEGIN CERTIFICATE-----
MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
pu/xO28QOG8=
-----END CERTIFICATE-----

I’ll try pinning both.

Why?
They may not be part of your chain!!!
Comodo has dozens
That was just my best guess.
post your intermediate cert
or PM it to me if your super ultra paranoid of showing completely public information

Like this one:

Be very careful. There is a very high probability to completely block the access to your website to some of your visitors if you make a mistake. And if that happened, you can't do anything about it until the end of the max-age period.

I can only advice for HPKP in two situation:

  • The security of your website a very critical and you prefer to risk to block the access than having a state-actor impersonating your website
  • Your website is a small personal website and you don't care of some people can't access it

More resources about that:

If you really decide to go that way, I strongly encourage you to generate at least two other private key, that you keep safe (not on your current server), to pin them.

There are others mechanisms that can help secure your website and are less dangerous than HPKP:

  • HSTS + preload
  • DNSSEC + CAA

And, thanks to chrome, if somebody target your website with a valid certificate, it will show up in Certificate Transparency logs: https://crt.sh

1 Like

Because you have to have pins for your backup private key (which would be sent to a secondary CA and an alternate backup key in order to be truly safe so that if you have any problem, the new CA will already be in your visitors’ browser caches.)

Do you still want the root Comodo CA file?
If so, post the intermediate.

Or do you want to put pins in place before you even get a Comodo cert?
If so, there is no way to know which chain they will be signing from in the future and for your specific request.

Yeah I’m fine with using the intermediate because I’m not using it until I have a problem.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.