Wednesday, August 31, 2011

HSTS preloading, public key pinning and Chrome

I recently discovered Chrome's net-internals. Among other interesting things, there is a tab about HTTP Strict Transport Security (HSTS). Also, introduced with version 12/13, Chrome now has HSTS preloading and public key pinning. These two features helps improve online security:
  • HSTS preloading: browser already knows if a site has to be contacted via HTTPS only. For instance, it ensures users go to https://site even if they type http://site. This protects against MITM attacks when reaching http://site for the first time (see how sslstrip strips the "s" of https).
  • public key pinning: certificate chain must include a whitelisted public key. For instance, it ensures only whitelisted Certificate Authorities (CA) can sign certificates for *.example.com, and not any CA in your browser store.

Enable HSTS

You can enable HSTS in Apache with mod headers and a line in your configuration:
<IfModule mod_headers.c>
  # this domain should only be contacted in HTTPS for the next 6 months
  Header add Strict-Transport-Security "max-age=15768000"
</IfModule>
Note: you can append "; includeSubDomains" if you want HSTS to inherit to subdomains (I do not, because this blog is at blogger). Restart Apache and check if it works:
$ curl -si https://stalkr.net | grep ^Strict
Strict-Transport-Security: max-age=15768000
Then you probably want to redirect all HTTP traffic to HTTPS with mod rewrite:
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTPS} off
  RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</IfModule>
Reference: Protecting your users from phishing with Apache rules and HSTS.

Chrome's HSTS interface

Using HSTS tab in Chrome version 13 and above, you can fully enjoy HSTS preloading and public key pinning.

With "query a domain", you can ask for a domain and it gives you its status in Chrome:
  • found: is this domain in local database?
  • mode: refers to HSTS mode of operation. STRICT enforces HTTPS connections while NONE allows HTTP connections. As far as I know, there are no other modes.
  • include_subdomains: do mode and pubkey_hashes propagate to subdomains?
  • domain: simply the domain considered.
  • is_preloaded: is this domain HSTS-preloaded in Chrome? For now it is hardcoded in the binary and will hopefully grow. You can contact Chromium to have your site included in that list.
  • pubkey_hashes: refers to public key pinning, it is the list of <hash algorithm>/<base64-encoded public key hash>

As an example, querying mail.google.com gives:
Found:
  mode: STRICT
  include_subdomains: true
  domain: mail.google.com
  is_preloaded: true
  pubkey_hashes: sha1/4n972HfV354KP560yw4uqe/baXc=,
    sha1/IvGeLsbqzPxdI0b0wuj2xVTdXgc=,
    sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=,
    sha1/AbkhxY0L343gKf+cki7NVWp+ozk=,
    sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=

Browsing to https://stalkr.net, Chrome receives the HSTS header and automatically adds the domain to the HSTS set, as we can see by querying stalkr.net:
Found:
  mode: STRICT
  include_subdomains: false
  domain: stalkr.net
  is_preloaded: false
  pubkey_hashes: (none)
is_preloaded: true confirms that the domain was not HSTS preloaded in Chrome but has been added by browsing.

Public key hashes

As well explained by Adam Langley (@agl__) in his post, public keys are hashed instead of certificates (more often reiussed). The hash is not the one of the public key bit string but the one of the SubjectPublicKeyInfo (SPKI) which can be obtained with openssl -pubkey.

Python script to create/view HTTP pins: http_pins.py.

So what is exactly the first hash in pubkey_hashes for mail.google.com?
$ python http_pins.py 4n972HfV354KP560yw4uqe/baXc=
SPKI fingerprint (sha1): e2:7f:7b:d8:77:d5:df:9e:0a:3f:9e:b4:cb:0e:2e:a9:ef:db:69:77
 

It looks like Verisign Class 3 Public Primary CA (cert), found in ca-certificates package:
$ python http_pins.py /etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem
/etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem:
* SPKI fingerprint (sha1): 65:e7:46:e3:e3:73:e3:a0:df:5f:5b:e8:8e:e2:73:e1:f2:fc:f7:92
* SPKI fingerprint (sha256): 08:56:e6:57:db:ef:1e:1a:bc:a4:b3:e8:75:0c:21:d3:c0:96:ba:b2:6b:d8:55:cb:9b:f4:86:76:5d:41:85:80
Public-Key-Pins: max-age=600; pin-sha1=ZedG4+Nz46DfX1vojuJz4fL895I=; pin-sha256=CFbmV9vvHhq8pLPodQwh08CWurJr2FXLm/SGdl1BhYA=
Confirmed by Chromium source code (search kGoogleAcceptableCerts).

What about the others?
$ python http_pins.py IvGeLsbqzPxdI0b0wuj2xVTdXgc=
SPKI fingerprint (sha1): 22:f1:9e:2e:c6:ea:cc:fc:5d:23:46:f4:c2:e8:f6:c5:54:dd:5e:07
 
VeriSign Class 3 Public Primary Certification Authority - G3 (cert), found in ca-certificates package.

$ python http_pins.py QMVAHW+MuvCLAO3vse6H0AWzuc0=
SPKI fingerprint (sha1): 40:c5:40:1d:6f:8c:ba:f0:8b:00:ed:ef:b1:ee:87:d0:05:b3:b9:cd
 
Google Internet Authority (1024 bits) (cert), not found in ca-certificates package.

$ python http_pins.py AbkhxY0L343gKf+cki7NVWp+ozk=
SPKI fingerprint (sha1): 01:b9:21:c5:8d:0b:df:8d:e0:29:ff:9c:92:2e:cd:55:6a:7e:a3:39
 
Google Internet Authority (2048 bits), not found in ca-certificates package and I was unable to find it elsewhere.

$ python http_pins.py SOZo+SvSspXXR9gjIBBPM5iQn9Q=
SPKI fingerprint (sha1): 48:e6:68:f9:2b:d2:b2:95:d7:47:d8:23:20:10:4f:33:98:90:9f:d4
 
Equifax Secure CA (cert), found in ca-certificates package.


Adding your own public key hashes?

Here are the SHA1 hashes of public keys of CAcert's root and class3 certificates, the CA I have chosen:
$ python http_pins.py root.crt class3.crt 
/tmp/root.crt:
* SPKI fingerprint (sha1): 10:da:62:4d:ef:41:a3:04:6d:cd:ba:3d:01:8f:19:df:3d:c9:a0:7c
* SPKI fingerprint (sha256): 6f:28:51:40:9d:71:05:04:a3:51:15:ab:cb:9a:6d:d3:f2:57:7e:c9:37:c9:ef:19:38:92:6f:a8:2f:d6:ff:5d
/tmp/class3.crt:
* SPKI fingerprint (sha1): f0:61:d8:3f:95:8f:4d:78:b1:47:b3:13:39:97:8e:a9:c2:51:ba:9b
* SPKI fingerprint (sha256): bd:0d:07:29:6b:43:fa:e0:3b:64:e6:50:cb:d1:8f:5e:26:71:42:52:03:51:89:d3:e1:26:3e:48:14:b4:da:5a
Public-Key-Pins: max-age=600; pin-sha1=ENpiTe9BowRtzbo9AY8Z3z3JoHw=;
  pin-sha256=byhRQJ1xBQSjURWry5pt0/JXfsk3ye8ZOJJvqC/W/10=;
  pin-sha1=8GHYP5WPTXixR7MTOZeOqcJRups=;
  pin-sha256=vQ0HKWtD+uA7ZOZQy9GPXiZxQlIDUYnT4SY+SBS02lo=

Then on HSTS tab, I choose "Add domain":
  • Domain: stalkr.net
  • Include subdomains: no
  • Public key fingerprints: sha1/ENpiTe9BowRtzbo9AY8Z3z3JoHw=, sha1/8GHYP5WPTXixR7MTOZeOqcJRups=
Querying the domain shows the entry has been successfully added to the HSTS set, with expected publickey_hashes values.

Unfortunately, net-internals HSTS tab is just a debugging interface and browsing to https://stalkr.net will reset the pubkey_hashes field. According to Adam, this is intended behaviour to allow sites to unpin themselves if pinning via HSTS is implemented. So right now, the only solution to pin public keys of CAs signing your website certificates would be to contact Chromium team to be included in the code.
I am looking forward pinning via HSTS (or any other solution) so that even small sites can enjoy pinning without requiring to be hardcoded in Chrome source.

Update: Tor Project just got pinned, and according to Chris Evans (@scarybeasts), Chris Palmer (@nocombat) "is looking at permitting sites to pin themeselves via an HTTP header, but it's hairy (think of the failure modes!)". Awesome!

Update: RFC draft is published (I updated the script above to reflect the new format). The backup key requirement is a bit annoying.

Update: it's now live in Chrome 18!

Conclusion

HSTS preloading is good to ensure your site is HTTPS only, and will never be contacted in HTTP. Public key pinning helps increase security in the broken world of SSL/TLS certificates: only listed CAs can sign certificates for the domain, not all CAs in the key store.

Other interesting projects:

7 comments:

  1. Conditional for header insertion cites wrong module. Should be mod_headers.

    ReplyDelete
  2. www.google.com actually has OPPORTUNISTIC instead of STRICT for the mode. Didn't find any information on that mode...

    ReplyDelete
  3. Since Chromium is open source, you can have more information on this by looking at the source code.
    The chrome://net-internals/#hsts page telling you "OPPORTUNISTIC" is done by this code: http://code.google.com/searchframe#OAMlx_jo-ck/src/chrome/browser/resources/net_internals/hsts_view.js&exact_package=chromium&q=opportunistic&type=cs&l=163

    Then you can view the code describing what is this mode "1": http://code.google.com/searchframe#OAMlx_jo-ck/src/net/base/transport_security_state.h&exact_package=chromium&q=DomainState&type=cs&l=51 it's just the default.

    So opportunistic means HSTS is not enforced: this means you can reach google.com without https - which is true.

    However, try to look up mail.google.com, it is in mode STRICT meaning HSTS is enforced: you cannot access it without https.

    ReplyDelete
  4. https://github.com/StalkR/misc/blob/master/http_pins.py not found.

    Any chance you'd mind resharing? I'm trying to implement cert pinning in C#, but I don't think I'm hashing the SPKI properly. I'm having trouble generating a matching hash for Equifax Secure CA.

    Any help would be greatly appreciated.

    ReplyDelete
  5. Public Key Pinning can be a problem for visitors with wrong dates on their computers. See https://www.dnswatch.info/articles/public-key-pinning-date-problem

    The draft needs to be reworked.

    ReplyDelete
    Replies
    1. https://www.youtube.com/watch?v=pWdd6_ZxX8c

      I think it's working as intended that things break if you're date setting is wrong. Date is important for this stuff that expires, and that's why we have automatic ways to set it like ntp.
      Now ntp isn't with its own issues, and that's an interesting topic to fix.

      Delete