Completed
Pull Request — master (#271)
by
unknown
03:48
created

SSL   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 136
Duplicated Lines 5.88 %

Coupling/Cohesion

Components 0
Dependencies 0

Importance

Changes 0
Metric Value
dl 8
loc 136
rs 10
c 0
b 0
f 0
wmc 21
lcom 0
cbo 0

3 Methods

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