Can I use Public-Key-Pins with LetsEncrypt?

Can I setup Public-Key-Pins when I setup a cronjob to renew the LetsEncrypt certificate every 30 days?

If the certificate is renewed then the Public-Key-Pin is also renewed right?


Solution 1:

Some words of caution to begin with:

  • Know what you're doing, if you're planing to implement HPKP.
  • If you don't do it right, or "if bad things happen", which aren't under your control, you might render your domain unusable.
  • Be sure to test your setup with a domain which isn't that important, or with a very short cache time, like ten seconds for example.
  • Think about which certs you would like to pin: Root, intermediate, leaf...
  • Think about if it makes sense to pin another (backup) certificate authority, it's intermediate certificates and your leaf certificate.
  • Better safe than sorry: Think about if it makes sense to pin another backup CA / cert, to have two.
  • If these points and questions sound "new" to you: Read what HPKP is about and the common traps and caveats, before you implement this.

Can I use Public-Key-Pins with LetsEncrypt?

  • Yes.

If the certificate is renewed then the Public-Key-Pin is also renewed right?

  • This depends on which certificate you're referring to and which cert(s) you're pinning.

Solution 2:

Would echo everything that gf_ said.

However, to answer the question, yes you can.

By default Let's Encrypt recreates the key and the cert at renewal. This makes implementing HPKP difficult if you want to pin at the leaf, which you probably should do in case intermediate changes (like it did in March 2016).

So you've several options around this if you still want to do HPKP:

  1. Use your own fixed CSR rather than the standard client which creates the CSR for you each time so the leaf key doesn't change. This blog post explains how to do this specifically because the author uses HPKP.
  2. Use short HPKP expiries and renew within that expiry time and change policy to have both old and new keys before actually changing the cert, with enough time for new policy to be picked up by any visitors.
  3. Pin the Let's Encrypt root instead of the leaf or cert.

Solution 3:

I just implemented this using the dehydrated client with dns01 validation. The dns01 hook is certzure because our DNS is hosted in Azure.

Edit: when I talk about private keys, obviously I always mean that you only turn the public key parts into pins. The private keys, as the name suggests, should always remain private. See my own hook for implementation details.


You need private key rollover to make this possible. That is, you always have the current private key (call it A) and the future private key (call it B) at hand, so that you can add them both to your pins. So at this point your pins are A and B. When the day of cert renewal comes, private key A becomes obsolete and B becomes live. At the same time you get a new future private key, call it C. You regenerate your pin list so now it contains B and C. So that's how you roll your private keys over. dehydrated supports this now.

Also, you need a hook that is called every time you renew your certs and thus roll over your private keys. I implemented this on my own.

Finally, if I get this right, you gotta make sure that:

HPKP age x 2 < days between cert renewals

For example, if your HPKP age is 50 days and you renew certs every 30 days, a client who visited your site on day one will be stuck with private keys A and B, and you rolled over to B and C at day 31. Your server has B and C, the client has A and B, there's a match even on day 50 and the client opens the site correctly.

BUT let's see if HPKP age is 70 days. You renew certs every 30 days, and the client visited your site on day one, so again, it only has private keys A and B. You rolled over to B and C at day 31, and rolled over to C and D at day 61. Your server has C and D, the client has A and B, there's no match and the client is given the middle finger from day 61 until day 71, when its HPKP policy expires.


Another, probably safer, and certainly much simpler option is using the same private key every time and generating one or several backup private keys, then hardcoding these into your HPKP config and be done with it.


Yeah, it's tricky and there might be caveats I haven't thought of, but we'll see in the long run. Obviously I deployed it on an uncritical subdomain with short (15 days) HPKP age so that it doesn't cause huge troubles.


Edit: I've written a few scripts to help you setting up HPKP with Let's Encrypt and dehydrated using Nginx:

HPKPinx