Your HTTPS Setup is Broken III: Patch it with HPKP

In the first blogpost of this series, I’ve shown you how easy it is for an attacker to eavesdrop the SSL/TLS connection between you and your client. This is not a theoretical issue and happens to customers every day. Even strong ciphers and encryption settings don’t help. Why? The problem is trust: If your client trusts any server, it doesn’t matter which cyphers your server is using.

This serious issue (mainly caused by bad Certificate Authorities (CAs)) didn’t stop since my first post. They’re getting even worse: Blue Coat, a company which is suspected to sell their TLS man-in-the-middle products to repressive regimes, have obtained a Intermediate CA certificate from Symantec, which would allow them to create valid certificates for any domain.

End of June, Comodo exposed an HTML injection vulnerability, which allowed any user to obtain a valid certificate for any domain for free.

The first thing you should do, is to enforce HTTPS usage via HTTP Strict Transport Security (as shown in my second post). It tells the browser to accept only encrypted sessions, which mitigates a downgrade attack to HTTP.

The second thing you should do, is to enable HPKP (HTTP Public Key Pinning), which protects your HTTPS connection from attacks involving these fraudulent certificates.

HPKP (HTTP Public Key Pinning)

While HSTS assures that the browser won’t open unencrypted HTTP requests to your domain, HPKP assures that nobody can exchange your certificate as a man-in-the-middle.

HPKP stands for HTTP Public Key Pinning. Like the name says, it’s pinning the public keys of all allowed certificates for your website. When the customer visits your website for the first time, his browser remembers the given keys for your certificates. The next time he visits you, the browser compares the cached keys with those in the current certificate chain. If they mismatch, he will see a warning page with no option to add an exception. This might seem a little bit harsh, but it’s exactly what you want: If somebody is eavesdropping on your customer’s secrets, you should stop transmitting them, without exception.

This picture shows, what a regular user sees in case of untrusted connections:

How does it work?

Like HSTS, HPKP is a simple HTTP header, which is included in the server’s response:

Public-Key-Pins: max-age=<expire-time>; pin-sha256="<base64-key>"; pin-sha256="<base64-backup-key>" [; includeSubdomains] [; report-uri="<report-URI>"]


  1. max-age Number of seconds the browser should cache this directive

  2. pin-sha256 The SHA-256 representation of the public key of your certificate. You have to provide at least two pins, one for the current certificate and one as backup

  3. includeSubdomains (optional) When provided, it tells the browser that these pins are valid for the subdomains, too

  4. report-uri (optional) A URI where reports about HPKP and its violations will be send to.


Like HSTS, HPKP is based on Trust On First Use (TOFU). Its assumption is that the first usage (first visit) is made within a secure connection. If you then get into a man-in-the-middle situation within <expire-time>, you are safe. While this method does not guarantee 100% safety, it provides one benefit: you don’t need a complex trust infrastructure which might introduce additional security issues or leads to lower user acceptance because of cumbersome handling.

The backup certificate

HPKP requires you to provide at least a second pin, which is not in use, yet. If you have to exchange your certificate, you won’t lock out your clients. This certificate can be created, but must not be in use, yet. It does not have to be signed yet by an authority.

Many blogposts out there recommend to pin your CA’s root certificate. If you have read my first blogpost, you know that many CAs are not that trustworthy. I have to remind you, that I was able to obtain a valid certificate for a foreign domain from the same CA that originally signed this domain! I would maybe make an exception for CAs which use only trustworthy methods for validation (like Let’s Encrypt, who uses DNS or HTTP resource domain validation only). But still you have to be sure that your CA doesn’t get hacked at all…


If enabled, the client’s browser will send a JSON document about current and pinned certificates to the given URI. Be advised to use a different domain for reporting otherwise you won’t get any information in case of failures for the current domain (which are the most interesting reports…).

Currently, reporting works only in Chrome and not in Firefox.

How to configure HPKP in NGINX

First, you need the base64 representation of your public key:

openssl x509 -in my-certificate.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

For extraction from CSR/private key/current domain, you can have a look at MDN.

Since HPKP is a simple header, you can then just use the add_header directive, which is available in the ngx_http_headers_module:

add_header Public-Key-Pins 'pin-sha256="vDGd5BIsPtpEDVrOzMypcp9CjSQ8QIiIQq6iRg59UOg="; pin-sha256="Mfyz5Zy4hGa1yrs93hMGGPo57r42fM+mttvEmHuXIdI="; max-age=60; includeSubdomains; report-uri=""';

This will pin the given keys for 60 seconds to the current domain, including it’s subdomains and the report will be send to the given URI.

Test it!

If you haven’t enabled HPKP yet, you should definitely give it a try! But probably you don’t want to test this stuff on your production system. Even with a short amount of max-age, it could prevent users to access your server. The nasty part about HPKP configuration errors are: You won’t recognize them, because the browser is advised to close the connection immediately…

To the rescue: I’ve setup a vagrant box for local testing, where you can configure and test as much as you want. I also had a credit voucher for an SSL certificate, so I’ve spent it to add a valid certificate and private key for the testing domain, which is valid until 29th January 2017.

Feel free to use it:

Once you have setup your production server, you can test it with SSL-Labs.


Acceptance: Public Key Pinning is not supported by every browser. Currently, Firefox, Chrome and Opera support HPKP, which leads to a global acceptance of almost 60% (or 66% in Germany). Of course, unsupported browsers still work with your domain, leaving you unprotected of man-in-the-middle attacks.

Manually added CAs: Firefox and Chrome disable Public Key Pinning mechanisms if a CA is installed manually on the device. This is due to antivirus applications or TLS proxies in big companies, which I’ve already mentioned in the first blogpost. Those applications play man-in-the-middle decrypting the traffic, scan it for security issues (hopefully not more), and re-encrypt it, before sending it to the original destination. For re-encryption they need a valid certificate for all domains, which must be trusted by a Certificate Authority. The trick is: They create their own CA and install its root certificate on all employee’s devices (or the device with the antivirus app installed).

If the browsers wouldn’t ignore those CAs, those connections would immediately fail and the users would start to use IE again…

Firefox has gladly an option to enable HPKP even for manually added CAs: In about:config, just set security.cert_pinning.enforcement_level to 2, (0=disabling at all, 1=ignore for manual CAs (default)). If you use an antivirus application, which scans your HTTPS traffic, you should now disable this feature.

Of course, the Antivirus application could do the HPKP checks, but they don’t.


When choosing the expire time, you have to be careful: If you set it too short, the probability of a successful man-in-the-middle attack increases after the cache is expired, because the user didn’t re-visited your website in the meantime. On the other hand, you must never set it higher than one of your pinned certificates is valid.

In other words: you have to ensure that at least one of your pinned certificates is at all times longer valid than your expiry time. Otherwise, you will lockout some of your users!


Client Certificates: Since HTTPS encryption happens on both sides of the connection, you can circumvent man-in-the-middle attacks by using client certificates. Users have to create their own personal certificate (doesn’t have to be signed by an CA) and give it to you. For further connections you can request the user’s certificate. Because the server knows which certificates belong to your users, you trust the certificate directly and not any CA.

Although modern browsers make it easy to create client certificates, average users are not used to deal with certificates.

DANE: (DNS-based Authentication of Named Entities) You store your certificate in a DNS record, which must be secured via DNSSEC. The clients must only trust this certificate (or a CA). Although i think this concept is very promising, it is not yet widely adopted: DNSSEC is not yet distributed widely and Chrome/Firefox only check DANE via special addons.

TLS Pinning Ticket: Similar to HPKP’s approach, this draft pins data on first use with a given lifetime. But it doesn’t pin publicly available data like certificate fingerprints, but a secret key, the client must validate against on next visit. Compared to HPKP, this happens on protocol level (TLS), therefore it works not only for HTTPS, but for all other TLS-based protocols. Additionally, it’s much easier to deploy. Currently, it’s in a very early draft status but has very interesting ideas.


In my opinion, you can not trust the CAs (or at least not all of them), so you have to do something against eavesdropping or manipulating your supposedly secure connections.

HTTP Public Key Pinning is a very easy and transparent solution to put trust back into your HTTPS traffic. Although only 60-66% of your users are protected, it’s better than nothing. I’ve shown you, how easy it is to implement, but you have to plan your certificate lifecycle carefully to prevent a lockout.