TLS Public Key Pinning with PHP + Curl?

4.6k views Asked by At

I would like to set up communication between two applications that are secured via TLS 1.2 wherein the public keys of the end-points are pinned. (No certificate authorities involved.)

Further, I don't even want to deal with certificates; just RSA/ECDSA public keys.

Specifically, they are both PHP applications and I'm using curl to facilitate the communication.

Has anyone accomplished this before?

3

There are 3 answers

1
Lucy On BEST ANSWER

(No certificate authorities involved.)

By default, curl is setup to not trust any CAs. So there's that. And without going into great detail or opinion based responses, here's a well organized "Pinning Cheat Sheet" that may be of some help to you: https://www.owasp.org/index.php/Pinning_Cheat_Sheet (no bounty necessary <3)

3
Electronick On

I didn't know the solution, but I can offer you some directions to discover:

  1. Take a look at http://php.net/manual/en/function.curl-setopt.php Particularly at CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 option. It's not allow to verify full public key, just its md5 hash.

  2. As another variant, you can implement your own service dealing with CURL through console. It's allow you to pass any options to curl. It's out of default curl library.

2
rugk On

Finally this feature has been implemented in PHP and you can also use it in older PHP versions as long as your cURL version is sufficient!

Requirements

  • PHP v7.0.7 or higher (a trick is shown below to make it work with earlier versions)
  • cURL v7.39 or higher

Preparation (Getting pins)

You can use SHA-256 hashes of the public key(s) in DER format for pinning. Do not confuse this with the certificate fingerprints! You'll pin the public keys, not the certificate here.

It's best to referrer to the official cURL documentation for extracting this keys. However there might be an easier way: The format of the pins is the same as the one used in HPKP. Thus if the webserver uses HPKP I strongly recommend you to use the hashes it sends there! You can simply copy and paste them from the header. You just have to change the format a bit as shown below.

Public-Key-Pins: pin-sha256="cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs="; pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="; max-age=5184000;

gets to:

sha256//cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=;sha256//M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE

However please note that there is one big difference between HPKP and CURL's CURLOPT_PINNEDPUBLICKEY: In HPKP you can pin the public keys of intermediate certificates, when using CURL you currently cannot do this!

As there are already many tutorials for HPKP you can also find other good guides for creating these hashes. Here is e.g. an one-liner using an existing certificate file by Scott Helme:

openssl x509 -pubkey < tls.crt | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

The format of the option finally is shown below. You can pin as much keys as you want and I would also recommend pinning at least one backup key, which is not currently in use, so you can easily switch keys in case of a server breach or something similar.

sha256//Base64EncodedHashOfPublicKey;sha256//Base64EncodedHashOfAnotherPublicKey

Alternatively you can also specify a path to a certificate file instead if you want.

Using in PHP

In PHP you can afterwards use it like this:

<?php
// this line makes it possible to use that option in PHP < 7.0.7
defined('CURLOPT_PINNEDPUBLICKEY') || define('CURLOPT_PINNEDPUBLICKEY', 10230);

$ch = curl_init("https://example.com");
curl_setopt($ch, CURLOPT_PINNEDPUBLICKEY, "YourPinsHere!!");
// ...

Afterwards you should test it by specifying an invalid pin. If it does not fail either your requirements are not met or you did an error when implementing it. You can also test it on the console with this command:

curl https://example.com --pinnedpubkey "YourPinsHere!!"