Everything you Need to Know about HTTP Public Key Pinning (HPKP)

Key pinning comprises the most practical hope for TLS security over the next few years, making targeted Certificate Authority-based attacks much riskier. While we wait for new systems built on top of key pinning, HTTP Public Key Pinning (HPKP) allows website operators to perform opportunistic key pinning, today.

Chain of Trust

Identity, not encryption, is the most important component of a cryptographic protocol: The best encryption in the world is worthless if you aren't speaking to whom you intend. SSL/TLS verifies identity through a chain of trust represented by a series of X.509 public key certificates. Your browser trusts a set of root certificates owned by Certificate Authorities; those Certificate Authoritess in turn extend their trust to the websites you visit. When you visit rlove.org, your browser verifies the certificate chain starting with rlove.org's own, moving down to the root certificate. If your browser ultimately trusts that root, you know you are talking to me and not an adversary.

A Giant Hole

A flaw in this system is that any compromised root certificate can in turn subvert the entire identity model. If I steal the Crap Authority's private key and your browser trusts their certificate, I can forge valid certificates for any website. In fact, I could execute this on a large scale, performing a man-in-the-middle (MITM) attack against every website that every user on my network visits. Indeed, this happens.

Nothing short of pre-shared keys are a perfect solution to such MITM attacks. This blog post, nonetheless, describes a technology that can significantly decrease the risk of MITM by allowing website operators to limit the certificates that can participate in their website's chain of trust and to detect in-progress attacks.

Public Key Pinning

HPKP is a draft IETF standard that implements a public key pinning mechanism via HTTP header, instructing browsers to require a whitelisted certificate for all subsequent connections to that website. This can greatly reduce the surface area for an MITM attack: Down from any root certificate to requiring a specific root, intermediate certificate, or even your exact public key.

The HTTP Header

The HPKP header looks like this:

Public-Key-Pins: pin-sha256="XXX"; pin-sha256="YYY"; max-age=ZZZ

where XXX is the base64-encode of the SHA256 hash of the public key to whitelist for this site; YYY is a backup of the same; and ZZZ is the time-to-live in seconds for the whitelist. The header may also provide the includeSubdomains directive, instructing browsers to apply the whitelist to all hosts on this domain, and the report-uri=X directive, which instructs browsers to report pin validation failures to X.

You may specify as many pin-sha256 directives as you wish. Only one fingerprint must be in your trust chain. Additionally, you must specify at least one fingerprint that is not included in your current chain. In other words, the standard forces you to have a backup pinned key.

What to Pin

The obvious and most secure public key to pin is your own. This will require fastidious management of your backup keys, however, and can be difficult if you rotate or revoke your certificates often. A simpler but less secure approach is pinning the first intermediate certificate in your trust chain i.e. one certificate up from your site's certificate. This will allow you to easily manage your own certificates while still greatly limiting the surface of any MITM attack.

Generating the SPKI Fingerprint

HPKP requires a base64-encode of a SHA256 hash of the public key you wise to pin. You can generate these easily given a public key, a certificate signing request (CSR), or a X.509 certificate. You only need one.

Given the public key pub.key:

openssl rsa -pubout -in pub.key -outform der | \
openssl dgst -sha256 -binary | \
base64

Given the CSR my.csr:

openssl req -noout -in my.csr -pubkey | \
openssl rsa -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64

Or given the PEM-encoded certificate certificate.pem:

openssl x509 -noout -in certificate.pem -pubkey | \
openssl rsa -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64

These commands will output a single string that looks something like this:

LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=

You'll drop that in a pin-sha256 directive. You can repeat this process for each key you wish to pin—for example, if you have two possible certificates for your site, you would pin both. As discussed above, you will also need a pin-sha256 directive with a backup key. Browsers enforce this by requiring one such directive to contain a fingerprint not in your current certificate chain.

Deploying

Because a misconfigured HPKP header can render your website inaccessible, rollout requires caution. Some tips:

  • Start with a max-age value of minutes and gradually increase it as you gain confidence.
  • Use the Public-Key-Pins-Report-Only header to generate failure reports to report-uri without enforcing pinning.
  • Maintain backup pins, preferably with a different Certificate Authority. Test them.

Web Server Configuration

HPKP requires no special HTTP server support; you just need to add a new header. For Apache:

Header set Public-Key-Pins "pin-sha256=\"XXX\"; pin-sha256=\"YYY\"; max-age=ZZZ"

Nginx:

add_header Public-Key-Pins 'pin-sha256="XXX"; pin-sha256="YYY"; max-age=ZZZ';

For more general information on configuring strong SSL/TLS support for Apache and Nginx, see my previous blog post and its follow up.

Verification

Verification is nontrivial as the draft standard requires a successful key validation before storing a pinned fingerprint. Thus, you can't serve a gibberish fingerprint to verify pinning failure. Nor does successfully connecting to your website mean the pin is actually stored. The easiest way to verify success is via Chrome's network internals page at chrome://net-internals/#hsts. Under Query domain, enter your website, and verify that your keys' fingerprints are listed under dynamic_spki_hashes. Chrome will store them only if they successfully validated.

Reporting

The Public Key Pinning specification includes a reporting mechanism. Key validation failures are reported to the URI specified by the optional report-uri directive. This is useful not only for debugging your own use of Public Key Pinning but also for detecting MITM attacks against your users. In fact, the mere possibility of reporting makes Public Key Pinning a deterrent against MITM attacks.

Reporting is performed via an HTTP POST of the following JSON message:

{
  "date-time": date-time,
  "hostname": hostname,
  "port": port,
  "effective-expiration-date": expiration-date,
  "include-subdomains": include-subdomains,
  "noted-hostname": noted-hostname,
  "served-certificate-chain": [
    pem1, ... pemN
  ],
  "validated-certificate-chain": [
    pem1, ... pemN
  ],
  "known-pins": [
    known-pin1, ... known-pinN
  ]
}

Browser Support

HPKP is supported by Chrome 38, Firefox 35, and newer.

Shortcomings

There are several shortcomings to HPKP, although on balance I still recommend its use. The single largest flaw is that HPKP is a trust-on-first-use (TOFU) mechanism. A user will not be able to detect an MITM attack on their first connection to a site. To repeatedly MITM a user, however, an adversary would need to intercept all connections to the target website. Given the nature of today's threats, HPKP thus provides reasonable protection against attacks. Notably, a global adversary would need to know whether a target had an HPKP pinning stored in their browser before initiating an MITM attack, lest the target's browser aggressively warn of (and potentially report) the failure.