Public Key Pinning (HPKP) with NetScaler

I recently decided to implement HTTP Public Key Pinning (HPKP) for some of our external facing Services and our NetScaler Gateway/Access Gateway.

Before we go on I recommend reading through the Wikipedia Article and this Blogpost from Tim Taubert to get a basic understanding of how HPKP works. Tim can also explain it a lot better than I can.

In this Guide I'm assuming you already have some kind of Load balancing vServer or NetScaler Gateway set up and running (including an existing SSL Certificate). So let's start:

In my Example I choose to pin the used Certificate itself and the Intermediate Certificate from RapidSSL where the Certificate itself was issued from. Please make sure you know the consequences when choosing the Certificate Hashes you are going to pin.

First off we need to generate the SHA256 Hashes for the Certificates we want to "pin". I choose to do it via the OpenSSL Interface in the NetScaler GUI but you could also choose to do the same via the NetScaler Shell CLI.

Just copy and paste the following Command into the Command Window and let OpenSSL do its magic. Copy or write down the created Hash as we will need it later on.

x509 -in /nsconfig/ssl/name-of-your-servercertificate.cer -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Rinse and repeat for the second Certificate (the Backup Hash) you want to pin. In my case this is the issuing Intermediate Certificate from RapidSSL.

x509 -in /nsconfig/ssl/name-of-your-intermediate.cer -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Next up we need to create the Rewrite Action and the Rewrite Policy to insert the needed HPKP HTTP Header into the HTTP Responses for our vServers and/or NetScaler Gateways.

First lets create a Rewrite Action. I named mine "insert_HPKP_header":

Name: insert_HPKP_header
Type: INSERT_HTTP_HEADER
Header Name: Public-Key-Pins
Expression: "pin-sha256=\"yourcerthashgoeshere\"; pin-sha256=\"yourbackuphashgoeshere\"; max-age=60; includeSubDomains"

Important: For the max-age I recommend starting with a low Value like 60 seconds during the Implementation Phase because if you somehow fuck up your Hashes you are only locked out for 60 Seconds. After successfully testing your HPKP Headers you should then ramp it up to something like 5184000 seconds.

Next Step is to create the needed Rewrite Policy itself. Mine is called enforce_HPKP and is just using TRUE as Expression.

Now its time to bind the newly created Rewrite Policy onto the vServer and/or the NetScaler Gateway Server. If you have multiple Rewrite Actions with different Priorities bound to the vServer (like in my case) make sure to set the "Goto Expression" Option to NEXT or otherwise only the first Rewrite Action will be applied. 

Last but not least you should check if the Public-Key-Pins Header is added successfully to your HTTP Responses. An easy Way is using the SSLLabs.com Scanner and check the Public Key Pinning Test. If everything is working you can now ramp up the max-age Value to 5184000.

If you don't like the NetScaler GUI you can also use the following CLI Commands to implement it:

add rewrite action insert_HPKP_header insert_http_header Public-Key-Pins q{"pin-sha256=\"yourcerthashgoeshere\"; pin-sha256=\"yourbackuphashgoeshere\"; max-age=60; includeSubDomains"} 
add rewrite policy enforce_HPKP TRUE insert_HPKP_header
bind vpn vserver nameofyourvserver -policy enforce_HPKP -priority 100 -gotoPriorityExpression END -type RESPONSE

Update: Implement HPKP Reporting

HPKP includes a Reporting Functionality for the Clients (Browsers) to send a Report in case of an Error. There is a great and free Service called report-uri.io from Scott Helme you can use to avoid having to set up your own Report Server.

After registering at report-uri.io you are given a unique Report URL you have to add to the HTTP HPKP Header as an additional Parameter. In this Example we would use the Public-Key-Pins-Report-Only Header (instead of the Public-Key-Pins Header) without enforcing HPKP itself. This would be a good first Step to see if your calculated Certificate Hashes in the Header are correct without blocking your Site in case of a wrong Hash.

Name: insert_HPKP_header_reportonly
Type: INSERT_HTTP_HEADER
Header Name: Public-Key-Pins-Report-Only
Expression: "pin-sha256=\"yourcerthashgoeshere\"; pin-sha256=\"yourbackuphashgoeshere\"; max-age=60; includeSubDomains; report-uri=\"https://report-uri.io/report/667d4f42bdc27b357863fefdd41574a4\""

The CLI Command for creating the would be

add rewrite action insert_HPKP_header_reportonly insert_http_header Public-Key-Pins-Report-Only q{"pin-sha256=\"yourcerthashgoeshere\"; pin-sha256=\"yourbackuphashgoeshere\"; max-age=2592000; includeSubDomains; report-uri=\"https://report-uri.io/report/667d4f42bdc27b357863fefdd41574a4\""}
add rewrite policy enable_HPKP_Reporting TRUE insert_HPKP_header_reportonly
bind vpn vserver nameofyourvserver -policy enable_HPKP_Reporting -priority 100 -gotoPriorityExpression END -type RESPONSE 

 As always Feedback and Comments are greatly appreciated.