Scott Helme recently published a how-to on HPKP (HTTP Public Key Pinning), which got me thinking about some of the finer details. I’ll assume you’re familiar with the basic idea and go straight into five points worthy of highlighting that arise from Scott’s article and the specification itself.
1. It’s not a standard yet
[See update below]
HPKP is still a draft and as such is likely to be in a greater state of flux than your “average” standard. Last year it went through 11 changes. Of course that’s not to say that every change has been (and will be) critical, but keep an eye on it, as something may change that affects your implementation. For example, version 10 (Feb 2014) dropped SHA-1: currently only SHA-256 is honoured. So if your HPKP header is based on a SHA-1 hash then it’s invalid.
2. What exactly is being pinned?
The next thing to point out is that we’re not pinning certificates, that’s why it’s HPKP and not HCP or something. The reasons for not pinning certificates are discussed elsewhere. What’s actually pinned is the Subject Public Key Info (SPKI) field. According to the X509 standard, this field is “used to carry the public key and identify the algorithm with which the key is used”. Because SPKI sounds a bit abstract, I’ll just use “public key” from now on.
What this means is that you can renew your SSL certificate without any thought to HPKP as long as your CA renews the same public key. On the other hand, if you want to refresh your keys at renewal time, you’ll have to update your HPKP pins, as even if you use your back-up key for the renewal, you’ve then lost your back-up. But I’m implying here that you’ve only got two pins…
Part of the checks for accepting a new pin is that at least one public key in the certificate chain for the connection is submitted for pinning, along with at least one public key that is NOT in the chain, which is your back-up. From this it’s clear that you can pin any one of the public keys in the certificate chain – from the root to the leaf (or more) – and you can have as many back-ups as you like. The flip-side to this is that the browser doesn’t know which pins are active and which are back-ups so, when validating a pinned connection, it’s a case of OR, not AND. In other words, only one of the stored pins needs to match one of the public keys in the presented certificate chain and thus adding more active pins does not mean that a larger part of the certificate chain must be matched for validation to succeed.
4. Updating the policy
The HPKP policy for a host is not set in stone until the expiry date: it can be refreshed with every new valid header – and in fact the policy can be removed altogether with
max-age=0. Obviously the public key pins themselves can be updated following validation too.
5. You can trial it safely
HPKP contains a reporting function whereby a failed validation can be reported to a URI set by the
report-uri directive in the HPKP header, e.g.
Public-Key-Pins: max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; report-uri="http://example.com/pkp-report"; includeSubDomains
Early on in its development the specification added a PKP-RO (Public-Key-Pins-Report-Only) header that, as the name suggests, instructs the browser not to enforce the policy, only to report any validation failures, e.g.
Public-Key-Pins-Report-Only: pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; report-uri="http://example.com/pkp-report"
In this way any mistakes aren’t punished with a failed connection but your report-uri page receives details on what went wrong. Note that in this mode any
max-age value is ignored (so it isn’t set above) because browsers don’t store the pinning details for report-only directives. But this could lead to a situation where someone (who doesn’t realise this) sets the PKP-RO header with the
includeSubDomains directive and assumes that no reporting means everything is working great. In fact, because the validation is performed on the current connection only, the PKP-RO header must always be present wherever validation is needed. So ensure the PKP-RO header is being set throughout your domain space.
I should add that the report-only mode is not mandatory so this feature may be browser-dependent. By the way, you can actually set both headers simultaneously: the specification states that they should be treated independently.
Due to point 1, all of this is subject to change!
HPKP became a standard in April 2015 – RFC 7469