6
from setuptools.extern.six.moves import urllib, http_client, map
9
from pkg_resources import ResolutionError, ExtractionError
17
'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths',
22
/etc/pki/tls/certs/ca-bundle.crt
23
/etc/ssl/certs/ca-certificates.crt
24
/usr/share/ssl/certs/ca-bundle.crt
25
/usr/local/share/certs/ca-root.crt
27
/System/Library/OpenSSL/certs/cert.pem
28
/usr/local/share/certs/ca-root-nss.crt
29
/etc/ssl/ca-bundle.pem
33
HTTPSHandler = urllib.request.HTTPSHandler
34
HTTPSConnection = http_client.HTTPSConnection
35
except AttributeError:
36
HTTPSHandler = HTTPSConnection = object
38
is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection)
42
from ssl import CertificateError, match_hostname
45
from backports.ssl_match_hostname import CertificateError
46
from backports.ssl_match_hostname import match_hostname
48
CertificateError = None
51
if not CertificateError:
53
class CertificateError(ValueError):
57
if not match_hostname:
59
def _dnsname_match(dn, hostname, max_wildcards=1):
60
"""Matching according to RFC 6125, section 6.4.3
62
http://tools.ietf.org/html/rfc6125#section-6.4.3
68
# Ported from python3-syntax:
69
# leftmost, *remainder = dn.split(r'.')
70
parts = dn.split(r'.')
74
wildcards = leftmost.count('*')
75
if wildcards > max_wildcards:
76
# Issue #17980: avoid denials of service by refusing more
77
# than one wildcard per fragment. A survey of established
78
# policy among SSL implementations showed it to be a
80
raise CertificateError(
81
"too many wildcards in certificate DNS name: " + repr(dn))
83
# speed up common case w/o wildcards
85
return dn.lower() == hostname.lower()
87
# RFC 6125, section 6.4.3, subitem 1.
88
# The client SHOULD NOT attempt to match a presented identifier in which
89
# the wildcard character comprises a label other than the left-most label.
91
# When '*' is a fragment by itself, it matches a non-empty dotless
94
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
95
# RFC 6125, section 6.4.3, subitem 3.
96
# The client SHOULD NOT attempt to match a presented identifier
97
# where the wildcard character is embedded within an A-label or
98
# U-label of an internationalized domain name.
99
pats.append(re.escape(leftmost))
101
# Otherwise, '*' matches any dotless string, e.g. www*
102
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
104
# add the remaining fragments, ignore any wildcards
105
for frag in remainder:
106
pats.append(re.escape(frag))
108
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
109
return pat.match(hostname)
111
def match_hostname(cert, hostname):
112
"""Verify that *cert* (in decoded format as returned by
113
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
114
rules are followed, but IP addresses are not accepted for *hostname*.
116
CertificateError is raised on failure. On success, the function
120
raise ValueError("empty or no certificate")
122
san = cert.get('subjectAltName', ())
123
for key, value in san:
125
if _dnsname_match(value, hostname):
127
dnsnames.append(value)
129
# The subject is only checked when there is no dNSName entry
131
for sub in cert.get('subject', ()):
132
for key, value in sub:
133
# XXX according to RFC 2818, the most specific Common Name
135
if key == 'commonName':
136
if _dnsname_match(value, hostname):
138
dnsnames.append(value)
139
if len(dnsnames) > 1:
140
raise CertificateError("hostname %r "
141
"doesn't match either of %s"
142
% (hostname, ', '.join(map(repr, dnsnames))))
143
elif len(dnsnames) == 1:
144
raise CertificateError("hostname %r "
146
% (hostname, dnsnames[0]))
148
raise CertificateError("no appropriate commonName or "
149
"subjectAltName fields were found")
152
class VerifyingHTTPSHandler(HTTPSHandler):
153
"""Simple verifying handler: no auth, subclasses, timeouts, etc."""
155
def __init__(self, ca_bundle):
156
self.ca_bundle = ca_bundle
157
HTTPSHandler.__init__(self)
159
def https_open(self, req):
161
lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req
165
class VerifyingHTTPSConn(HTTPSConnection):
166
"""Simple verifying connection: no auth, subclasses, timeouts, etc."""
168
def __init__(self, host, ca_bundle, **kw):
169
HTTPSConnection.__init__(self, host, **kw)
170
self.ca_bundle = ca_bundle
173
sock = socket.create_connection(
174
(self.host, self.port), getattr(self, 'source_address', None)
177
# Handle the socket if a (proxy) tunnel is present
178
if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None):
181
# http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7
182
# change self.host to mean the proxy server host when tunneling is
183
# being used. Adapt, since we are interested in the destination
184
# host for the match_hostname() comparison.
185
actual_host = self._tunnel_host
187
actual_host = self.host
189
self.sock = ssl.wrap_socket(
190
sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
193
match_hostname(self.sock.getpeercert(), actual_host)
194
except CertificateError:
195
self.sock.shutdown(socket.SHUT_RDWR)
200
def opener_for(ca_bundle=None):
201
"""Get a urlopen() replacement that uses ca_bundle for verification"""
202
return urllib.request.build_opener(
203
VerifyingHTTPSHandler(ca_bundle or find_ca_bundle())
210
def get_win_certfile():
212
if _wincerts is not None:
213
return _wincerts.name
216
from wincertstore import CertFile
220
class MyCertFile(CertFile):
221
def __init__(self, stores=(), certs=()):
222
CertFile.__init__(self)
226
atexit.register(self.close)
230
super(MyCertFile, self).close()
234
_wincerts = MyCertFile(stores=['CA', 'ROOT'])
235
return _wincerts.name
238
def find_ca_bundle():
239
"""Return an existing CA bundle path, or None"""
241
return get_win_certfile()
243
for cert_path in cert_paths:
244
if os.path.isfile(cert_path):
248
return certifi.where()
249
except (ImportError, ResolutionError, ExtractionError):