A script to simplify HPKP

HPKP, or HTTP Public Key Pinning, is one of the very few ways to deal with fraudulent or impersonated certificates. Unfortunately it’s considered too risky by most to deploy publicly, and those who do choose to employ it often do so incorrectly. In fact, a recent Netcraft article points out that of all web servers surveyed, a paltry 0.09% of them utilized HPKP. Of those, a further ~25% didn’t configure their header correctly, leaving the mechanism totally ineffective for its intended purpose. It’s hard to blame those who got it wrong. After all, there are a plethora of minor details involved with setting up HPKP that are easy to overlook. It just so happens that I’ve written a script to make the process a whole lot easier to get right.

pinned lock

HPKP is a risky proposition

HTTP Public Key Pinning is an essentially exercise in compromise. There are no other mechanisms, as of the time of this writing, which can effectively prevent HTTPS hijacking in the manner that HPKP does. But it has some major drawbacks which have severely hampered its widespread adoption:

  • It requires that the end user connects to the legitimate site at least once prior to connecting to a counterfeit site or intermediary.
  • Even if an end user has already stored HPKP information for a site which is hijacked, it must still be within the time frame of the Max-Age directive as specified by pinning headers. Otherwise the browser will have disregarded the pinned fingerprints and won’t be aware anything is amiss.
  • The information is sent via HTTP headers, which means that the implementation is purely HTTPS-specific. It can’t supplement TLS in any other context.
  • Mishandling of or technical mistakes when creating keys is always a serious faux pas, but with HPKP it can be downright catastrophic.
  • If there IS a severe mishap involving HPKP, the result can be devastating. In the worst case, clients who’ve previously connected to the affected site will be unable to connect to it in a normal manner until their HTTP public pinning policy either expires or is manually removed by the user (ouch).
  • Proper implementation of HPKP requires better processes and organizational controls than most institutions are capable of (though their corporate charters would certainly indicate otherwise).

I won’t go into too much more detail here, as I’ve already written at some length on the subject. But for all of my naysaying, joined to the chorus of cynics among the 99.91% who haven’t implemented HPKP, it really does have its uses. If your site requires security as a foremost consideration, HPKP might just be what you need. For instance, my Gen&Send service is utterly, completely pointless without bulletproof HTTPS. In that case, the benefits of HPKP outweighs the risk. And, I might add, I’ve been running gensend.com with HPKP (along with a myriad of other optional security augmentations) for many years without any problems.

If you’re careful, and I mean very, very careful, HTTP Public Key Pinning can work just fine for you too. But if you run a sloppy shop, you may as well stop reading here. HPKP is just going to bite you in the posterior sooner or later. After all, one shouldn’t bother installing better locks on a house with broken windows.

The script

Because I do utilize HPKP for my security-centric sites, and fairly often, it’s occurred to me that the process is a bit of a pain in the… well, there are a lot of moving parts, especially concerning OpenSSL, and a little typo can make a big difference. Especially when you’re performing the same process three times instead of once. I wrote the hpkp-generator.sh script to ease that margin for error, eliminate a whole lot of pedantic syntax checking and, last but not least, save me a bunch of time.

NOTE: The hpkp-generator.sh script was designed to prepare a new site for HPKP deployment from the beginning, before the site even has HTTPS implemented, etc. Thus it will mint three brand new sets of CSRs, keys and base64-encoded SHA256 sums. If you already have a live HTTPS certificate on your website which you don’t intend to replace and wish to use this script, disregard the first key, CSR, and sum the script generates for you and substitute the base64 fingerprint for the key of your live cert instead. Or, if you’d prefer to pin four keys rather than three, you can append your active certificate’s base64-encoded SHA256 fingerprint to the Public-Key-Pins header along with the new ones.

Requirements

There are a few requirements to running hpkp-generator.sh:

  • BASH. You’re gonna need a system with a proper bash shell to run the script as-is. If /bin/bash points to anything but, you know, BASH, you may run into strange problems.
  • OpenSSL. Relatively up-to-date openssl is required (that’s one of those tools which should never be outdated on your system anyway, IMO).
  • Base64. This is something you can expect to “just be there” on anything unix-y. But, you need it. So make sure you have it.
  • Entropy. Yes, you need chaos, lots of chaos. Without sufficient entropy available, OpenSSL blocks; it’ll just idle until there’s enough in the pool to finish. On certain systems, this can take a really long time. Other systems are equipped with an HRNG, which will provide substantial randomness. That’s the ideal solution. If an HRNG isn’t at your disposal, there’s also haveged which uses your CPU’s timing jitter as a supplementary entropy source. Otherwise, the process can take a while. Maybe put on a pot of coffee.

I’d also strongly recommend NOT running this script on your actual web heads. In fact, I’d suggest never storing your standby keys on public-facing systems at all until you’re actually deploying them to swap out your previous production key. The whole idea is that they are backups in case something goes wrong with the live one. In other words, don’t put all your eggs in one basket.

Getting the script

You can download the script directly, or preview the code first. I prefer to have scripts I frequently use in my path, which might go something like this:

sudo wget https://files.tuxhelp.org/scripts/hpkp-generator.sh -O /usr/local/bin/hpkp-generator.sh &&\
sudo chown root:root /usr/local/bin/hpkp-generator.sh &&\
sudo chmod 755 /usr/local/bin/hpkp-generator.sh

But the script itself doesn’t require any special privileges, so anywhere is fine.

Running the script

This is actually the easy part. There are some variables you can tweak within the script if you wish, but the defaults are what most people will want to stick with anyway.

Screenshot of hpkp-generator.sh's variablesThe various directories don’t have to be separate if you don’t want them to be, either. Just set them all to be the same path, absolute or relative. It doesn’t matter really; whatever works best for your needs.

In order to actually generate your keys, CSRs, and SHA256 fingerprints, we obviously need to run the script, e.g.

hpkp-generator.sh

From here on it’s interactive. Answer all of the normal questions you’d answer when making a certificate signing request. The most important is obviously the CN, or common name (in this case the domain name), for which you’ll be requesting a signed cert. Some CA’s may treat certain fields as optional, depending on whether on the CA and the type of cert being issued. In any case, it doesn’t hurt to fill it all in. Worst case scenario, the CA will ignore any subject fields it doesn’t care about.

Before proceeding, the script will ask you to verify that your information is correct. Take a moment to read it over carefully prior to continuing. If something’s incorrect, you can press ‘N‘ to re-enter it.

At this point, the script will do the hard part. Using the information you’ve entered, it will automatically generate a series of CSRs, keys and calculate each of the SHA256 sums you’ll need for HPKP.

When that’s all taken care of, it will spit out a series of example config snippets for several webservers. Namely, Hiawatha, NGINX, Apache, and Lighttpd.

If your particular web server of choice isn’t listed, or if you’re using an older/newer version where the syntax is different, refer to the official documentation for your web server on how to correctly implement HPKP headers. Whatever the case, applying the appropriate configuration syntax to your servers is your own responsibility.

Deploying HPKP

Depending on the nature of your situation — whether you’re part of an organization or an individual, how many servers you have, whether they’re clustered or stand-alone, behind load balancers, etc. — the process of deploying HPKP will vary. But essentially, you’ve now got a directory (or series of directories) which contain CSRs, keys, and SHA256 sums.

  • The CSRs will be used to request new certs from a valid CA. You’ll only need to issue against the first one, not the standby CSRs, to start with. In fact, if all goes well, you may never need to dust off your backup CSRs or keys. Unless you have a reason not to, you can continue to issue against the first CSR as long as you wish. If a cert/key or CA is compromised along the way, just issue against the first standby CSR and pull the deprecated key out of your pinning header.
  • The key will accompany your public cert chain. If you’ve ever deployed HTTPS before, this is a process you should already be very familiar with. It’s only slightly different here. In this case you have three keys; obviously the first key will accompany the cert issued in respect to the first CSR, the first standby key will accompany the first standby CSR, and so on.
  • The SHA256 sums will be added to your HTTPS headers. The script makes recommendations on what correct syntax should look like for several web servers, but make sure you use the proper structure for your unique situation. Check, double-check, triple-check, in fact do all the checks on your settings before going live with HPKP.
  • Test, test, and test some more. This cannot be overstated, especially if there’s a large dollar value attached to your web presence. There is even a special version of the header implicitly for testing HPKP. If you want to test live without fully committing to your HPKP setup, use the Public-Key-Pins-Report-Only header instead of Public-Key-Pins header in your server’s configuration.
  • Take care of your backups! Your standby keys and CSRs are your lifeline in case something goes wrong with your primary one. Treat them with the utmost care. Make backups of your backups. Store them securely, someplace you or somebody else within the chain of custody can actually access them if you need them. And it should go without saying that you should NOT be storing them on public-facing servers, or they’ll be just as vulnerable as the ones they’re acting as backups for.

In closing…

HTTP Public Key Pinning is certainly not for everyone. It requires attention to detail, reasonable processes, and better than average technical savvy. I mean, Bank of America is currently the US’ 2nd largest bank at well over $2 trillion in value, and yet they can’t even seem to deploy basic HTTPS correctly.

BofA sends their root cert to clients, which is pointless. A novice misconfiguration.

But if you find that the security needs of your site calls for HPKP, don’t be afraid: just be methodical. After all, it’s not rocket science; it’s only computer science. And I hope my script makes it all a little less stressful for you.

One thought on “A script to simplify HPKP

Leave a Reply

Your email address will not be published. Required fields are marked *