Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TLS Public Key Pinning with PHP + Curl?

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?

like image 479
Scott Arciszewski Avatar asked Nov 24 '14 19:11

Scott Arciszewski


People also ask

Is PHP Curl secure?

Curl is as secure as a normal HTTP request.

Why is HPKP deprecated?

Due to HPKP mechanism complexity and possibility of accidental misuse, browsers deprecated and removed HPKP support in favor of Certificate Transparency and its Expect-CT header.

How do you mitigate SSL pinning?

SSL Pinning Bypass can be prevented using two-way SSL authentication. Using this technique, application acts as SSL client and send its certificate to the SSL server to validate after SSL server validates itself to the SSL client.

What is Dynamic SSL pinning?

The SSL pinning (or public key, or certificate pinning) is a technique mitigating Man-in-the-middle attacks against the secure HTTPS communication. The typical Android solution is to bundle the hash of the certificate, or the exact data of the certificate into the application.


3 Answers

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!!"

like image 95
rugk Avatar answered Oct 18 '22 20:10

rugk


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.

like image 38
Electronick Avatar answered Oct 18 '22 18:10

Electronick


(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)

like image 2
Lucy Avatar answered Oct 18 '22 20:10

Lucy