From bdda99c0c10b0906849f2c53f28e53ef10829f7a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 27 Dec 2011 23:14:54 -0500 Subject: [PATCH] urllib3 update --- requests/packages/urllib3/connectionpool.py | 8 ++- .../packages/urllib3/packages/__init__.py | 4 ++ .../packages/ssl_match_hostname/__init__.py | 61 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 requests/packages/urllib3/packages/__init__.py create mode 100644 requests/packages/urllib3/packages/ssl_match_hostname/__init__.py diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index be9b7fe..c1ebed4 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -13,6 +13,7 @@ from Queue import Queue, Empty, Full from select import select from socket import error as SocketError, timeout as SocketTimeout +from .packages.ssl_match_hostname import match_hostname, CertificateError try: import ssl @@ -70,7 +71,8 @@ class VerifiedHTTPSConnection(HTTPSConnection): self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, cert_reqs=self.cert_reqs, ca_certs=self.ca_certs) - + if self.ca_certs: + match_hostname(self.sock.getpeercert(), self.host) ## Pool objects @@ -364,6 +366,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # SSL certificate error raise SSLError(e) + except (CertificateError), e: + # Name mismatch + raise SSLError(e) + except (HTTPException, SocketError), e: # Connection broken, discard. It will be replaced next _get_conn(). conn = None diff --git a/requests/packages/urllib3/packages/__init__.py b/requests/packages/urllib3/packages/__init__.py new file mode 100644 index 0000000..37e8351 --- /dev/null +++ b/requests/packages/urllib3/packages/__init__.py @@ -0,0 +1,4 @@ +from __future__ import absolute_import + +from . import ssl_match_hostname + diff --git a/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py b/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py new file mode 100644 index 0000000..9560b04 --- /dev/null +++ b/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py @@ -0,0 +1,61 @@ +"""The match_hostname() function from Python 3.2, essential when using SSL.""" + +import re + +__version__ = '3.2.2' + +class CertificateError(ValueError): + pass + +def _dnsname_to_pat(dn): + pats = [] + for frag in dn.split(r'.'): + if frag == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + else: + # Otherwise, '*' matches any dotless fragment. + frag = re.escape(frag) + pats.append(frag.replace(r'\*', '[^.]*')) + return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules + are mostly followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_to_pat(value).match(hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_to_pat(value).match(hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") -- 2.34.1