-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Rename RootCaBundleBuilder to CaRootPemBundle - Separate MozillaCertData class - Better OOP design / easier to test - Increase unit test coverage
- Loading branch information
1 parent
729f034
commit 78f7044
Showing
12 changed files
with
507 additions
and
24,356 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
<?php | ||
// Generated by ZF2's ./bin/classmap_generator.php | ||
return array( | ||
'Sslurp\Module' => __DIR__ . '/Module.php', | ||
'Sslurp\X509Certificate' => __DIR__ . '/src/Sslurp/X509Certificate.php', | ||
'Sslurp\RootCaBundleBuilder' => __DIR__ . '/src/Sslurp/RootCaBundleBuilder.php', | ||
'Sslurp\AbstractCaRootData' => __DIR__ . '/src/Sslurp/AbstractCaRootData.php', | ||
'Sslurp\MozillaCertData' => __DIR__ . '/src/Sslurp/MozillaCertData.php', | ||
'Sslurp\CaRootPemBundle' => __DIR__ . '/src/Sslurp/CaRootPemBundle.php', | ||
'Sslurp\X509Certificate' => __DIR__ . '/src/Sslurp/X509Certificate.php', | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
/** | ||
* This file is part of Sslurp. | ||
* https://github.com/EvanDotPro/Sslurp | ||
* | ||
* (c) Evan Coury <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
namespace Sslurp; | ||
|
||
use DateTime; | ||
use DateTimeZone; | ||
|
||
abstract class AbstractCaRootData | ||
{ | ||
/** | ||
* The CVS version ID | ||
* | ||
* @var string | ||
*/ | ||
private $version = null; | ||
|
||
/** | ||
* The date/time of the version commit | ||
* | ||
* @var DateTime | ||
*/ | ||
private $dateTime = null; | ||
|
||
/** | ||
* Get the version number | ||
* | ||
* @return string | ||
*/ | ||
public function getVersion() | ||
{ | ||
if ($this->version === null) { | ||
if (preg_match('/^#?\s?(CVS_ID\s+\".*\")/m', $this->getContent(), $match)) { | ||
$parts = explode(' ', $match[1]); | ||
$this->version = $parts[6]; | ||
$this->dateTime = new DateTime($parts[9] . ' ' . $parts[10], new DateTimeZone('UTC')); | ||
} else { | ||
throw new \RuntimeException('Unable to detect CVS version ID.'); | ||
} | ||
} | ||
|
||
return $this->version; | ||
} | ||
|
||
/** | ||
* Get the date/time of the last update | ||
* | ||
* @return DateTime | ||
*/ | ||
public function getDateTime() | ||
{ | ||
if ($this->dateTime === null) { | ||
$this->getVersion(); | ||
} | ||
|
||
return $this->dateTime; | ||
} | ||
|
||
abstract protected function getContent(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<?php | ||
/** | ||
* This file is part of Sslurp. | ||
* https://github.com/EvanDotPro/Sslurp | ||
* | ||
* (c) Evan Coury <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
namespace Sslurp; | ||
|
||
class CaRootPemBundle extends AbstractCaRootData | ||
{ | ||
/** | ||
* The content of the PEM bundle | ||
* | ||
* @var string | ||
*/ | ||
private $pemContent = null; | ||
|
||
public function __construct($pemContent = null, MozillaCertData $mozCertData = null) | ||
{ | ||
$this->pemContent = $pemContent; | ||
$this->mozCertData = $mozCertData ?: new MozillaCertData(); | ||
} | ||
|
||
/** | ||
* Return the content of the PEM bundle | ||
*/ | ||
public function getContent() | ||
{ | ||
if ($this->pemContent === null) { | ||
$this->pemContent = $this->getUpdatedCaRootBundle(); | ||
} | ||
return $this->pemContent; | ||
} | ||
|
||
public function isLatest() | ||
{ | ||
return $this->getVersion() == $this->mozCertData->getVersion(); | ||
} | ||
|
||
public function getUpdatedCaRootBundle() | ||
{ | ||
return $this->buildBundle($this->mozCertData->getContent()); | ||
} | ||
|
||
public function getMozillaCertData() | ||
{ | ||
return $this->mozCertData; | ||
} | ||
|
||
protected function buildBundle($rawCertData) | ||
{ | ||
$rawCertData = explode("\n", $rawCertData); | ||
$currentDate = defined('SSLURP_OVERRIDE_DATETIME') ? SSLURP_OVERRIDE_DATETIME : date(DATE_RFC822); | ||
$caBundle = <<<EOT | ||
## | ||
## Bundle of CA Root Certificates | ||
## | ||
## Generated $currentDate | ||
## Generated with Sslurp (https://github.com/EvanDotPro/Sslurp) | ||
## | ||
## This is a bundle of X.509 certificates of public Certificate Authorities | ||
## (CA). These were automatically extracted from Mozilla's root certificates | ||
## file (certdata.txt). This file can be found in the mozilla source tree: | ||
## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt' | ||
## | ||
## It contains the certificates in PEM format and therefore | ||
## can be directly used with curl / libcurl / php_curl, or with | ||
## an Apache+mod_ssl webserver for SSL client authentication. | ||
## Just configure this file as the SSLCACertificateFile. | ||
## | ||
EOT; | ||
$caName = ''; | ||
while (($line = array_shift($rawCertData)) !== null) { | ||
if (preg_match('/^#|^\s*$/', $line)) { | ||
continue; | ||
} | ||
|
||
$line = rtrim($line); | ||
|
||
if (preg_match('/^(CVS_ID\s+\".*\")/', $line, $match)) { | ||
$caBundle .= "# {$match[1]}\n"; | ||
} | ||
|
||
if (preg_match('/^CKA_LABEL\s+[A-Z0-9]+\s+\"(.*)\"/', $line, $match)) { | ||
$caName = $match[1]; | ||
} | ||
|
||
if (preg_match('/^CKA_VALUE MULTILINE_OCTAL/', $line)) { | ||
$data = ''; | ||
while ($line = array_shift($rawCertData)) { | ||
if (preg_match('/^END/', $line)) { | ||
break; | ||
} | ||
|
||
$line = rtrim($line); | ||
$octets = explode('\\', $line); | ||
array_shift($octets); | ||
|
||
foreach ($octets as $oct) { | ||
$data .= chr(octdec($oct)); | ||
} | ||
} | ||
|
||
$caBundle .= $this->buildPemString($caName, $data); | ||
} | ||
} | ||
|
||
return $caBundle; | ||
} | ||
|
||
protected function buildPemString($caName, $data) | ||
{ | ||
return "\n{$caName}\n" | ||
. str_repeat('=', strlen($caName)) . "\n" | ||
. "-----BEGIN CERTIFICATE-----\n" | ||
. chunk_split(base64_encode($data), 76, "\n") | ||
. "-----END CERTIFICATE-----\n"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
<?php | ||
/** | ||
* This file is part of Sslurp. | ||
* https://github.com/EvanDotPro/Sslurp | ||
* | ||
* (c) Evan Coury <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
namespace Sslurp; | ||
|
||
class MozillaCertData extends AbstractCaRootData | ||
{ | ||
// mxr.mozilla.org cert expires Nov 28th, 2013 | ||
const MOZILLA_MXR_SSL_PIN = '47cac6d8f2c2363675e6f433970f27523824d0ec'; | ||
|
||
/** | ||
* certdata.txt contents | ||
* | ||
* @var string | ||
*/ | ||
private $certData = null; | ||
|
||
/** | ||
* Stream context | ||
* | ||
* @var resource | ||
*/ | ||
private $context = null; | ||
|
||
/** | ||
* @param string $certData Used for unit testing | ||
*/ | ||
public function __construct($certData = null) | ||
{ | ||
$this->certData = $certData; | ||
} | ||
|
||
/** | ||
* Get the raw certdata.txt contents from mxr.mozilla.org | ||
* | ||
* @return string | ||
*/ | ||
public function getContent() | ||
{ | ||
if ($this->certData === null) { | ||
$this->certData = $this->fetchLatestCertData(); | ||
} | ||
|
||
return $this->certData; | ||
} | ||
|
||
/** | ||
* Get the stream context for the TCP connection to the server. | ||
* | ||
* If no stream context is set, will create a default one. | ||
* | ||
* @return resource | ||
*/ | ||
public function getStreamContext() | ||
{ | ||
if (! $this->context) { | ||
$this->context = stream_context_create(array('ssl' => array( | ||
'capture_peer_cert' => true, | ||
'verify_peer' => true, | ||
'allow_self_signed' => false, | ||
'cafile' => $this->getRootCaBundlePath(), | ||
'CN_match' => 'mxr.mozilla.org', | ||
))); | ||
} | ||
|
||
return $this->context; | ||
} | ||
|
||
protected function fetchLatestCertData() | ||
{ | ||
$ctx = $this->getStreamContext(); | ||
|
||
$fp = stream_socket_client('ssl:https://mxr.mozilla.org:443', $errNo, $errStr, 30, STREAM_CLIENT_CONNECT, $ctx); | ||
|
||
if (!$fp) { | ||
throw new \RuntimeException($errStr, $errNo); | ||
} | ||
|
||
$headers = "GET /mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1 HTTP/1.1\r\n"; | ||
$headers .= "Host: mxr.mozilla.org\r\n"; | ||
$headers .= "Connection: close\r\n"; | ||
$headers .= "Accept: */*\r\n"; | ||
fwrite($fp, "{$headers}\r\n"); // send request | ||
|
||
$response = ''; | ||
while (!feof($fp)) { | ||
$response .= fgets($fp); | ||
} | ||
fclose($fp); | ||
|
||
$params = stream_context_get_params($ctx); | ||
$cert = new X509Certificate($params['options']['ssl']['peer_certificate']); | ||
$pin = $cert->getPin(); | ||
|
||
if ($pin !== static::MOZILLA_MXR_SSL_PIN) { | ||
if (time() > 1383282000) { // If it's November 1st, 2013 or later (mxr.mozilla.org cert expires Nov 28th, 2013) | ||
echo "WARNING: mxr.mozilla.org certificate pin may be out of date. " . | ||
"If you see this, message, please file an issue at https://github.com/EvanDotPro/Sslurp/issues\n"; | ||
} else { | ||
echo "ERROR: Certificate pin for mxr.mozilla.org did NOT match expected value!\n\n"; | ||
echo 'Expected: ' . static::MOZILLA_MXR_SSL_PIN . "\n"; | ||
echo "Received: {$pin}\n"; | ||
exit(1); | ||
} | ||
} | ||
|
||
return $this->getResponseBody($response); | ||
} | ||
|
||
protected function getResponseBody($string) | ||
{ | ||
$lines = explode("\r\n", $string); | ||
|
||
$isHeader = true; | ||
$headers = $content = array(); | ||
|
||
while ($lines) { | ||
$nextLine = array_shift($lines); | ||
|
||
if ($isHeader && $nextLine == '') { | ||
$isHeader = false; | ||
continue; | ||
} | ||
if ($isHeader) { | ||
$headers[] = $nextLine; | ||
} else { | ||
$content[] = $nextLine; | ||
} | ||
} | ||
|
||
return implode("\r\n", $content); | ||
} | ||
|
||
protected function getRootCaBundlePath() | ||
{ | ||
$caBundlePaths = array( | ||
'/etc/pki/tls/certs/ca-bundle.crt', | ||
'/etc/ssl/certs/ca-certificates.crt', | ||
'/etc/ssl/ca-bundle.pem', | ||
'/usr/share/ssl/certs/ca-bundle.crt', | ||
__DIR__ . '/../../data/Equifax_Secure_Ca.pem', | ||
); | ||
|
||
foreach ($caBundlePaths as $caBundle) { | ||
if (is_readable($caBundle)) { | ||
break; | ||
} | ||
} | ||
|
||
return $caBundle; | ||
} | ||
} |
Oops, something went wrong.