Requests_SSL   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 128
Duplicated Lines 6.25 %

Coupling/Cohesion

Components 0
Dependencies 0

Importance

Changes 0
Metric Value
dl 8
loc 128
rs 10
c 0
b 0
f 0
wmc 20
lcom 0
cbo 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
B verify_certificate() 0 35 9
B verify_reference_name() 0 28 6
A match_domain() 8 25 5

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 View Code Duplication
		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
}
145