90
90
ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY
91
91
"""
92
92
93
-
import ipaddress
94
-
import re
95
93
import sys
96
94
import os
97
95
from collections import namedtuple
160
158
161
159
from socket import socket, AF_INET, SOCK_STREAM, create_connection
162
160
from socket import SOL_SOCKET, SO_TYPE
161
+
import socket as _socket
163
162
import base64 # for DER-to-PEM translation
164
163
import errno
165
164
import warnings
183
182
def _dnsname_match(dn, hostname):
184
183
"""Matching according to RFC 6125, section 6.4.3
185
184
186
-
http://tools.ietf.org/html/rfc6125#section-6.4.3
185
+
- Hostnames are compared lower case.
186
+
- For IDNA, both dn and hostname must be encoded as IDN A-label (ACE).
187
+
- Partial wildcards like 'www*.example.org', multiple wildcards, sole
188
+
wildcard or wildcards in labels other then the left-most label are not
189
+
supported and a CertificateError is raised.
190
+
- A wildcard must match at least one character.
187
191
"""
188
-
pats = []
189
192
if not dn:
190
193
return False
191
194
192
-
leftmost, *remainder = dn.split(r'.')
195
+
wildcards = dn.count('*')
196
+
# speed up common case w/o wildcards
197
+
if not wildcards:
198
+
return dn.lower() == hostname.lower()
199
+
200
+
if wildcards > 1:
201
+
raise CertificateError(
202
+
"too many wildcards in certificate DNS name: {!r}.".format(dn))
193
203
194
-
wildcards = leftmost.count('*')
195
-
if wildcards == 1 and len(leftmost) > 1:
204
+
dn_leftmost, sep, dn_remainder = dn.partition('.')
205
+
206
+
if '*' in dn_remainder:
196
207
# Only match wildcard in leftmost segment.
197
208
raise CertificateError(
198
-
"wildcard can only be present in the leftmost segment: " + repr(dn))
209
+
"wildcard can only be present in the leftmost label: "
210
+
"{!r}.".format(dn))
199
211
200
-
if wildcards > 1:
201
-
# Issue #17980: avoid denials of service by refusing more
202
-
# than one wildcard per fragment. A survey of established
203
-
# policy among SSL implementations showed it to be a
204
-
# reasonable choice.
212
+
if not sep:
213
+
# no right side
205
214
raise CertificateError(
206
-
"too many wildcards in certificate DNS name: " + repr(dn))
215
+
"sole wildcard without additional labels are not support: "
216
+
"{!r}.".format(dn))
207
217
208
-
# speed up common case w/o wildcards
209
-
if not wildcards:
210
-
return dn.lower() == hostname.lower()
218
+
if dn_leftmost != '*':
219
+
# no partial wildcard matching
220
+
raise CertificateError(
221
+
"partial wildcards in leftmost label are not supported: "
222
+
"{!r}.".format(dn))
211
223
212
-
# RFC 6125, section 6.4.3, subitem 1.
213
-
# The client SHOULD NOT attempt to match a presented identifier in which
214
-
# the wildcard character comprises a label other than the left-most label.
215
-
if leftmost == '*':
216
-
# When '*' is a fragment by itself, it matches a non-empty dotless
217
-
# fragment.
218
-
pats.append('[^.]+')
219
-
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
220
-
# RFC 6125, section 6.4.3, subitem 3.
221
-
# The client SHOULD NOT attempt to match a presented identifier
222
-
# where the wildcard character is embedded within an A-label or
223
-
# U-label of an internationalized domain name.
224
-
pats.append(re.escape(leftmost))
225
-
else:
226
-
# Otherwise, '*' matches any dotless string, e.g. www*
227
-
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
224
+
hostname_leftmost, sep, hostname_remainder = hostname.partition('.')
225
+
if not hostname_leftmost or not sep:
226
+
# wildcard must match at least one char
227
+
return False
228
+
return dn_remainder.lower() == hostname_remainder.lower()
228
229
229
-
# add the remaining fragments, ignore any wildcards
230
-
for frag in remainder:
231
-
pats.append(re.escape(frag))
232
230
233
-
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
234
-
return pat.match(hostname)
231
+
def _inet_paton(ipname):
232
+
"""Try to convert an IP address to packed binary form
233
+
234
+
Supports IPv4 addresses on all platforms and IPv6 on platforms with IPv6
235
+
support.
236
+
"""
237
+
# inet_aton() also accepts strings like '1'
238
+
if ipname.count('.') == 3:
239
+
try:
240
+
return _socket.inet_aton(ipname)
241
+
except OSError:
242
+
pass
243
+
244
+
try:
245
+
return _socket.inet_pton(_socket.AF_INET6, ipname)
246
+
except OSError:
247
+
raise ValueError("{!r} is neither an IPv4 nor an IP6 "
248
+
"address.".format(ipname))
249
+
except AttributeError:
250
+
# AF_INET6 not available
251
+
pass
252
+
253
+
raise ValueError("{!r} is not an IPv4 address.".format(ipname))
235
254
236
255
237
256
def _ipaddress_match(ipname, host_ip):
@@ -241,14 +260,19 @@ def _ipaddress_match(ipname, host_ip):
241
260
(section 1.7.2 - "Out of Scope").
242
261
"""
243
262
# OpenSSL may add a trailing newline to a subjectAltName's IP address
244
-
ip = ipaddress.ip_address(ipname.rstrip())
263
+
ip = _inet_paton(ipname.rstrip())
245
264
return ip == host_ip
246
265
247
266
248
267
def match_hostname(cert, hostname):
249
268
"""Verify that *cert* (in decoded format as returned by
250
269
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
251
-
rules are followed, but IP addresses are not accepted for *hostname*.
270
+
rules are followed.
271
+
272
+
The function matches IP addresses rather than dNSNames if hostname is a
273
+
valid ipaddress string. IPv4 addresses are supported on all platforms.
274
+
IPv6 addresses are supported on platforms with IPv6 support (AF_INET6
275
+
and inet_pton).
252
276
253
277
CertificateError is raised on failure. On success, the function
254
278
returns nothing.
@@ -258,7 +282,7 @@ def match_hostname(cert, hostname):
258
282
"SSL socket or SSL context with either "
259
283
"CERT_OPTIONAL or CERT_REQUIRED")
260
284
try:
261
-
host_ip = ipaddress.ip_address(hostname)
285
+
host_ip = _inet_paton(hostname)
262
286
except ValueError:
263
287
# Not an IP address (common case)
264
288
host_ip = None
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4