HTTP Public Key Pinning (HPKP) for mortals
Update 2017-10-27: Chrome decides to stop supporting HPKP mostly because of low usage, as I understand it. Anyways, it makes this learning kinda useless 😫.
Update 2019-01-17: It’s now decided it’s being removed in Chrome 72
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?
- Sometimes things seems to be harder than they are? (I have a bad feeling about this…)
- I know a few more
openssl
commands now - I learned that I was running my system’s default
openssl
and not the one I had installed withhomebrew
- There’s a
base64
command included in both Ubuntu 16.04 and OSX (I can imagine most UNIX derivates have this). Before this I always opened up anode
orirb
REPL to encode/decode :facepalm:. Now I know better 😸.