angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

SSL.php (3814B)


      1 <?php
      2 /**
      3  * SSL utilities for Requests
      4  *
      5  * @package Requests
      6  * @subpackage Utilities
      7  */
      8 
      9 /**
     10  * SSL utilities for Requests
     11  *
     12  * Collection of utilities for working with and verifying SSL certificates.
     13  *
     14  * @package Requests
     15  * @subpackage Utilities
     16  */
     17 class Requests_SSL {
     18 	/**
     19 	 * Verify the certificate against common name and subject alternative names
     20 	 *
     21 	 * Unfortunately, PHP doesn't check the certificate against the alternative
     22 	 * names, leading things like 'https://www.github.com/' to be invalid.
     23 	 *
     24 	 * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
     25 	 *
     26 	 * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
     27 	 * @param string $host Host name to verify against
     28 	 * @param array $cert Certificate data from openssl_x509_parse()
     29 	 * @return bool
     30 	 */
     31 	public static function verify_certificate($host, $cert) {
     32 		$has_dns_alt = false;
     33 
     34 		// Check the subjectAltName
     35 		if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
     36 			$altnames = explode(',', $cert['extensions']['subjectAltName']);
     37 			foreach ($altnames as $altname) {
     38 				$altname = trim($altname);
     39 				if (strpos($altname, 'DNS:') !== 0) {
     40 					continue;
     41 				}
     42 
     43 				$has_dns_alt = true;
     44 
     45 				// Strip the 'DNS:' prefix and trim whitespace
     46 				$altname = trim(substr($altname, 4));
     47 
     48 				// Check for a match
     49 				if (self::match_domain($host, $altname) === true) {
     50 					return true;
     51 				}
     52 			}
     53 		}
     54 
     55 		// Fall back to checking the common name if we didn't get any dNSName
     56 		// alt names, as per RFC2818
     57 		if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
     58 			// Check for a match
     59 			if (self::match_domain($host, $cert['subject']['CN']) === true) {
     60 				return true;
     61 			}
     62 		}
     63 
     64 		return false;
     65 	}
     66 
     67 	/**
     68 	 * Verify that a reference name is valid
     69 	 *
     70 	 * Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
     71 	 * - Wildcards can only occur in a name with more than 3 components
     72 	 * - Wildcards can only occur as the last character in the first
     73 	 *   component
     74 	 * - Wildcards may be preceded by additional characters
     75 	 *
     76 	 * We modify these rules to be a bit stricter and only allow the wildcard
     77 	 * character to be the full first component; that is, with the exclusion of
     78 	 * the third rule.
     79 	 *
     80 	 * @param string $reference Reference dNSName
     81 	 * @return boolean Is the name valid?
     82 	 */
     83 	public static function verify_reference_name($reference) {
     84 		$parts = explode('.', $reference);
     85 
     86 		// Check the first part of the name
     87 		$first = array_shift($parts);
     88 
     89 		if (strpos($first, '*') !== false) {
     90 			// Check that the wildcard is the full part
     91 			if ($first !== '*') {
     92 				return false;
     93 			}
     94 
     95 			// Check that we have at least 3 components (including first)
     96 			if (count($parts) < 2) {
     97 				return false;
     98 			}
     99 		}
    100 
    101 		// Check the remaining parts
    102 		foreach ($parts as $part) {
    103 			if (strpos($part, '*') !== false) {
    104 				return false;
    105 			}
    106 		}
    107 
    108 		// Nothing found, verified!
    109 		return true;
    110 	}
    111 
    112 	/**
    113 	 * Match a hostname against a dNSName reference
    114 	 *
    115 	 * @param string $host Requested host
    116 	 * @param string $reference dNSName to match against
    117 	 * @return boolean Does the domain match?
    118 	 */
    119 	public static function match_domain($host, $reference) {
    120 		// Check if the reference is blocklisted first
    121 		if (self::verify_reference_name($reference) !== true) {
    122 			return false;
    123 		}
    124 
    125 		// Check for a direct match
    126 		if ($host === $reference) {
    127 			return true;
    128 		}
    129 
    130 		// Calculate the valid wildcard match if the host is not an IP address
    131 		// Also validates that the host has 3 parts or more, as per Firefox's
    132 		// ruleset.
    133 		if (ip2long($host) === false) {
    134 			$parts    = explode('.', $host);
    135 			$parts[0] = '*';
    136 			$wildcard = implode('.', $parts);
    137 			if ($wildcard === $reference) {
    138 				return true;
    139 			}
    140 		}
    141 
    142 		return false;
    143 	}
    144 }