March 13, 2016 · security http ssl https

HPKP as another security layer

Using HTTP Public Key Pinning for SuperDuper ™ security

Happily, HTTP Strict Transport Security is very nice thing for trust-on-first-use sites. But there are one thing that haunts me sometimes - hacked CA. Sure, that's quite rare thing, but who knows.
So, some iranian guy's want to do some MIM. They hacked some CA, issue fake (but valid) ssl certificates and ... the end. Or not?

Nope, if HPKP is used and it's TOFU web site.
The idea is pretty simple:
when UA makes first trusted (I hope) connection to server - it receives HPKP header(s) and store certifiates SPKI fingerprints, ttl, and other validation options for that domain. Then, in all further connections UA will check certificate for identity (whole validation process descirbed in rfc7469.

HPKP headers looks like this:

Public-Key-Pins: max-age=2592000;  
       pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
       pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
       report-uri="http://example.com/pkp-report";
       includeSubDomains

where
max-age - ttl for storing headers and fingerprints
pin-sha256 - SPKI fingerprints (sha256 is only supported for now) for at least two certificates
report-uri - uri for send POST request in case of failed validation (optional)
includeSubDomains - include subdomains to pinned connections (optional)

Ok, it's a game time

Let's create Subject Public Key Info fingerprints for example.org (detailed about SPKI - here)
Easiest way is to create it from server certificate using openssl:

openssl s_client -servername example.org -connect example.org:443 | openssl x509 -pubkey -noout | oubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64  

Here it is

depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3  
verify return:1  
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X1  
verify return:1  
depth=0 CN = example.org  
verify return:1  
writing RSA key  
TCKhYNyE/2K3N+Latb1KNH/iXqLDjmAkZhCHzN5N20c=  

Now, it's time to add this key in headers and set ttl for pinning. But before doing this, we need another SPKI fingerprint for bad situation when original key was compromized. Due to rfc in headers must be at least two pin's. So, for correct pinning we need at least two private keys and certificates: original and back-up (preferable from another CA). Sure, no-one can forbid us to put some garbage in back-up pin(s), just for testing purposes.

About max-age

There are no limits for max-age, but UA's may set own limits. Guys from google recommends somethig around 60 days:

There is probably no ideal upper limit to the max-age directive that would satisfy all use cases. However, a value on the order of 60 days (5,184,000 seconds) may be considered a balance between the two competing security concerns.

About report-uri

Very cool feature as for me. UA will make POST request with useful info to specified uri when valid pinned connection can't be established. The little problem: only Chrome >=46 (and Chromium I suppose) can do this for now.

Sample HPKP-report from Chrome:

#headers
{ host: 'blog.roundside.com',
  connection: 'close',
  'content-length': '8710',
  pragma: 'no-cache',
  'cache-control': 'no-cache',
  'user-agent': 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36',
  'accept-encoding': 'gzip, deflate',
  'accept-language': 'en-US,en;q=0.8' }
#payload (certifiates was truncated)
{
"date-time":"2016-03-13T16:54:14.023Z",  
"effective-expiration-date":"2016-03-13T16:57:54.368Z",  
"hostname":"blog.roundside.com",  
"include-subdomains":false,  
"known-pins":[  
"pin-sha256=\"MbJIcRLFNfwcfRUpV46EtDyp0d8WO8o34sMALixkftU=\"",  
"pin-sha  
256=\"w5k2T/1B4J1cdxlhfUnpY1SIL1+n26NFv8fTdEs2ThM=\""],  
"noted-hostname":"blog.roundside.com",  
"port":443,  
"served-certificate-chain":[  
"-----BEGIN CERTIFICATE-----    \nMIIFCDCCA/CgAwIBAgISAUijX+5SwJtgdqwJUUDBNjrCMA0GCSqGSIb3DQEBCwUA\nMEoxCzAJBgNVBAYTAlVTMRYwFAYD....Y4=\n  
-----END CERTIFICATE-----\n",
"-----BEGIN CERTIFICATE-----  
\n...2k5xeua2zUk=\n
-----END CERTIFICATE-----\n"
],
"validated-certificate-chain":[  
"-----BEGIN CERTIFICATE-----\n  
...4=\n
-----END CERTIFICATE-----\n",
"-----BEGIN CERTIFICATE-----\n  
...2k5xeua2zUk=\n
-----END CERTIFICATE-----\n",
"-----BEGIN CERTIFICATE-----\n  
MII....UQ\n  
-----END CERTIFICATE-----\n"
]}

served-certificate-chain - is exactly certificate chain received from server.
validated-certificate-chain - is what UA trying to verify after first fail. Usually - the same as served-certificate-chain + Root CA certificate from UA.

Ok, so let's setup a proper header using SPKI fingerprints and recommended max-age:

Public-Key-Pins  
pin-sha256="TCKhYNyE/2K3N+Latb1KNH/iXqLDjmAkZhCHzN5N20c=";  
pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=";  
max-age=5184000;  
report-uri="http://example.org/pkp-report"  

Check it:

curl -I "https://example.org"  
HTTP/1.1 200 OK  
Public-Key-Pins: pin-sha256="TCKhYNyE/2K3N+Latb1KNH/iXqLDjmAkZhCHzN5N20c="; pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="; max-age=5184000; report-uri="http://example.org/pkp-report";  
Date: Mon, 14 Mar 2016 00:23:29 GMT  
Connection: keep-alive

Seems ok.

How to test?
Testing pinned connections is not trivial for now. The are some ways to verify HPKP status: in Firefox "Network" tab -> "Security": "Public Key Pinning", also HPKP Analyser is good tool.

For actually testing HPKP we need to replace pinned certificate to other valid certificate but not pinned. Just changing fingerpints doen't have any action coz UA can't update HPKP headers due certificate and fingerprint conflict and will keep old headers unchanged.

Ok, let's put not pinned certificate on this blog

Chrome reaction:

Firefox reaction:

The cool thing - there no "proceed anyway" button, which in very good for security.

So, HPKP & HSTS is not ideal but can greatly reduce MIM attacks.

Further reading:
RFC
MDN
Google
OWASP
HPKP Analyser

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket
Comments powered by Disqus