Certificate Pinning in PHP

UPDATED: The previous version of this script is affected by a security vulnerability in some PHP versions. Details are located at: https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html. I have updated the script below to include a version check, and if you are using this technique/code I strongly suggest you add similar version checks.

I spent a few hours today working on a piece of open source technology that should help address some of the peerjacking concerns with PHP developers. This tool will help me secure our ecommerce clients, as well as help improve the SSL ecosystem within PHP itself.

So without further introduction, certificate pinning in PHP. A reference design called sslurp (written by Evan Coury (@evandotpro)) and a pin.php pinning script. This builds on the work by Adam Langley and Moxie Marlinspike who did the heavy lifting in developing the pinning algorithm and have also published pinning scripts (written in GO and Python respectively). My PHP contribution to the pinning ecosystem makes it possible to set and verify pinned certs within PHP projects.


#!/usr/bin/php
<?php

/*
Copyright (c) 2012, StormTide Digital Studios Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the
      distribution.

   *  Neither the name of StormTide Digital Studios Inc, nor the names 
      of its contributors may be used to endorse or promote products 
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
SUCH DAMAGE.
*/

//Version 0.2 Alpha

//Check for vulnerable openssl_x509_parse call.

if(version_compare(PHP_VERSION'5.3.0''<')) {
  
//PHP Less than 5.3
  
die("PHP Version Insufficient. See https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html" PHP_EOL);
}
if(
version_compare(PHP_VERSION,'5.3.0','>=') && version_compare(PHP_VERSION,'5.3.28','<')) {
  
//If less than 5.3.28 in the 5.3 series.
  
die("PHP Version Insufficient. See https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html"PHP_EOL);
}
if(
version_compare(PHP_VERSION,'5.4.0','>=') && version_compare(PHP_VERSION,'5.4.23','<')) {
  
//If less than 5.4.23 in the 5.4 series.
  
die("PHP Version Insufficient. See https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html"PHP_EOL);
}
if(
version_compare(PHP_VERSION,'5.5.0','>=') && version_compare(PHP_VERSION,'5.5.7','<')) {
  
//If less than 5.5.7 in the 5.5 series.
  
die("PHP Version Insufficient. See https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html"PHP_EOL);
}

if(isset($_SERVER['argv']) && is_array($_SERVER['argv']) && !empty($_SERVER['argv'][1])) {
  
$cert $_SERVER['argv'][1];
} else {
  die(
"Error no cert provided. Usage ./pin.php cert.crt"PHP_EOL);
}

if(is_file($cert) && is_readable($cert)) {
  try {
   
$contents file_get_contents($cert);
   
$parsed openssl_x509_parse($contents);
   
$pubkey openssl_get_publickey($contents);
   
$pubkeydetails openssl_pkey_get_details($pubkey);
   
$pubkeypem $pubkeydetails['key'];
   
//Convert PEM to DER before SHA1'ing
   
$start '-----BEGIN PUBLIC KEY-----';
   
$end '-----END PUBLIC KEY-----';
   
$pemtrim substr($pubkeypem, (strpos($pubkeypem$start)+strlen($start)), (strlen($pubkeypem) - strpos($pubkeypem$end))*(-1));
   
$der base64_decode($pemtrim);
   
//Calculate the SHA1 pin of the Public Key in DER format.
   
$pin sha1($der);
   
$base64 base64_encode(sha1($der,true));
   echo 
'Pin for: '$parsed['name']. PHP_EOL;
   echo 
'Pin Hex (SHA1-long): '$pinPHP_EOL;
   echo 
'Pin Base64 (SHA1-short): '$base64PHP_EOL;
   echo 
'Pin Chrome format: sha1/'$base64PHP_EOL;
 } catch (
Exception $e) {
   die(
'Could not get public key pin');
 }
} else {
  die(
'Could not read cert');
}


This cli script will generate pins for SSL certificates and the reference design by Evan Coury for validating a pinned connection can be viewed at https://github.com/EvanDotPro/Sslurp/blob/master/src/Sslurp/RootCaBundleBuilder.php#L130

Additionally the sslurp package will, in the future, likely form the basis for bootstrapping a CA bundle into PHP projects requiring secure operation.