Back to index

HTTP Public Key Pinning (HPKP) for mortals

Published

UPDATE: Chrome decides to stop supporting HPKP mostly because of low usage, as I understand it. Anyways, it makes this learning kinda useless 😫.

Me, a “regular web developer”, who only know the basics of cryptography just implemented HPKP on a domain, x.bolmaster2.com, and I got surprised that it wasn’t much of a hassle. There’s a good chance I’ve missed something and probably the problems are gonna come when the Let’s encrypt certificate expires. That happens on 😬. A perfect day for dealing with certificates 🙌. Heh. Heh. Heh.

OT: I always try to spend the beginning of the year trying to think about my future and WTF I’m doing or/and wanna do with my life. So no, I don’t wanna spend that time “bikeshedding” on trying to understand what went wrong with a certificate or anything like that instead of trying to figure out the meaning of (my) life.

Anyways, Mozilla has great documention on this on MDN here and it’s from there and from Scott Helme including his service report-uri I learned most of this.

Generating the hash from the current certificate

This makes a hash out of my x509 certificate I’ve generated earlier with Let’s Encrypt awesome tool certbot:

openssl x509 -in /etc/letsencrypt/live/x.bolmaster2.com/cert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

As you don’t need the private key for this, you could as well get it straight from the website like this:

openssl s_client -servername x.bolmaster2.com -connect x.bolmaster2.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

…or use report-uri’s awesome service

Generating the backup certificate

Ok, so I first just thought that I could just create a totally independent certificate that was based on nothing at all. I mean, the backup’s need to be based one of the CA’s your certificate is coming from, otherwise it’s not gonna be trusted, even though it’s gonna work. In my case, it’s Let’s Encrypt. Here they describe their chain. So for example to get the hash based on Let’s Encrypt Authority X3 which is currently issuing certs:

curl https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem | openssl x509 -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

Generating your own private backup

So this is only needed if Let’s Encrypt lose there ability to issue certificates as I’ve understood it.

openssl req -new -sha256 -newkey rsa:4096 -nodes -out x.bolmaster2.com.csr -keyout x.bolmaster2.com.key

And then the sha256 hash from that key:

openssl rsa -in x.bolmaster2.com.key -outform der -pubout | openssl dgst -sha256 -binary | base64

So how does the header look?

Here is one with the main hash first and then the backup hash and a very low max-age to be able to recover fast if anything fails.

Public-Key-Pins: pin-sha256="bu/NZbVTIiYMwowXj5mi0TlewoCL15iRzz+3AHbRyOs="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=3600;

Learnings?