Skip to content

Commit

Permalink
Initial support for parsing common/alt names
Browse files Browse the repository at this point in the history
This appears to follow both the specification and browser behaviour, but this
should be double-checked before merge.
  • Loading branch information
rmccue committed Aug 11, 2013
1 parent 08e3638 commit 727de7b
Showing 1 changed file with 77 additions and 0 deletions.
77 changes: 77 additions & 0 deletions library/Requests/Transport/fsockopen.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function request($url, $headers = array(), $data = array(), $options = ar
$url_parts = parse_url($url);
$host = $url_parts['host'];
$context = stream_context_create();
$verifyname = false;

// HTTPS support
if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
Expand All @@ -55,7 +56,10 @@ public function request($url, $headers = array(), $data = array(), $options = ar

$context_options = array(
'verify_peer' => true,
// 'CN_match' => $host,
'capture_peer_cert' => true
);
$verifyname = true;

// SNI, if enabled (OpenSSL >=0.9.8j)
if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
Expand All @@ -73,6 +77,10 @@ public function request($url, $headers = array(), $data = array(), $options = ar
}
}

if (isset($options['verifyname']) && $options['verifyname'] === false) {
$verifyname = false;
}

stream_context_set_option($context, array('ssl' => $context_options));
}
else {
Expand All @@ -88,6 +96,10 @@ public function request($url, $headers = array(), $data = array(), $options = ar
$fp = stream_socket_client($remote_socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $context);
restore_error_handler();

if ($verifyname) {
$this->verify_certificate_from_context($host, $context);
}

if (!$fp) {
if ($errno === 0) {
// Connection issue
Expand Down Expand Up @@ -316,6 +328,71 @@ public function connect_error_handler($errno, $errstr) {
return true;
}

/**
* Verify the certificate against common name and subject alternative names
*
* Unfortunately, PHP doesn't check the certificate against the alternative
* names, leading things like 'https://www.github.com/' to be invalid.
* Instead
*
* @see http://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
* @param [type] $host [description]
* @param [type] $context [description]
* @return [type] [description]
*/
public function verify_certificate_from_context($host, $context) {
$meta = stream_context_get_params($context);
if (empty($meta['options']) || empty($meta['options']['ssl']) || empty($meta['options']['ssl']['peer_certificate'])) {
throw new Requests_Exception('SSL certificate was not given; check that the host is valid', 'fsockopen.ssl.no_params');
}

$cert = openssl_x509_parse($meta['options']['ssl']['peer_certificate']);

// Calculate the valid wildcard match if the host is not an IP address
$parts = explode('.', $host);
if (ip2long($host) === false) {
$parts[0] = '*';
}
$wildcard = implode('.', $parts);

$has_dns_alt = false;

// Check the subjectAltName
if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
$altnames = explode(',', $cert['extensions']['subjectAltName']);
foreach ($altnames as $altname) {
$altname = trim($altname);
if (strpos($altname, 'DNS:') !== 0)
continue;

$has_dns_alt = true;

// Strip the 'DNS:' prefix and trim whitespace
$altname = trim(substr($altname, 4));

if ($host === $altname) {
return true;
}
elseif ($wildcard === $altname) {
return true;
}
}
}

// Fall back to checking the common name if we didn't get any dNSName
// alt names, as per RFC2818
if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
if ($host === $cert['subject']['CN']) {
return true;
}
elseif ($wildcard === $cert['subject']['CN']) {
return true;
}
}

throw new Requests_Exception('SSL certificate did not match the requested domain name', 'fsockopen.ssl.no_match');
}

/**
* Whether this transport is valid
*
Expand Down

0 comments on commit 727de7b

Please sign in to comment.