Index: third_party/requests/packages/urllib3/contrib/pyopenssl.py |
diff --git a/third_party/requests/packages/urllib3/contrib/pyopenssl.py b/third_party/requests/packages/urllib3/contrib/pyopenssl.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5c4c6d8d31052b88426dd83aaf50ccea33f51126 |
--- /dev/null |
+++ b/third_party/requests/packages/urllib3/contrib/pyopenssl.py |
@@ -0,0 +1,167 @@ |
+'''SSL with SNI-support for Python 2. |
+ |
+This needs the following packages installed: |
+ |
+* pyOpenSSL (tested with 0.13) |
+* ndg-httpsclient (tested with 0.3.2) |
+* pyasn1 (tested with 0.1.6) |
+ |
+To activate it call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3`. |
+This can be done in a ``sitecustomize`` module, or at any other time before |
+your application begins using ``urllib3``, like this:: |
+ |
+ try: |
+ import urllib3.contrib.pyopenssl |
+ urllib3.contrib.pyopenssl.inject_into_urllib3() |
+ except ImportError: |
+ pass |
+ |
+Now you can use :mod:`urllib3` as you normally would, and it will support SNI |
+when the required modules are installed. |
+''' |
+ |
+from ndg.httpsclient.ssl_peer_verification import (ServerSSLCertVerification, |
+ SUBJ_ALT_NAME_SUPPORT) |
+from ndg.httpsclient.subj_alt_name import SubjectAltName |
+import OpenSSL.SSL |
+from pyasn1.codec.der import decoder as der_decoder |
+from socket import _fileobject |
+import ssl |
+ |
+from .. import connectionpool |
+from .. import util |
+ |
+__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] |
+ |
+# SNI only *really* works if we can read the subjectAltName of certificates. |
+HAS_SNI = SUBJ_ALT_NAME_SUPPORT |
+ |
+# Map from urllib3 to PyOpenSSL compatible parameter-values. |
+_openssl_versions = { |
+ ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, |
+ ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD, |
+ ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, |
+} |
+_openssl_verify = { |
+ ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, |
+ ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, |
+ ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER |
+ + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, |
+} |
+ |
+ |
+orig_util_HAS_SNI = util.HAS_SNI |
+orig_connectionpool_ssl_wrap_socket = connectionpool.ssl_wrap_socket |
+ |
+ |
+def inject_into_urllib3(): |
+ 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' |
+ |
+ connectionpool.ssl_wrap_socket = ssl_wrap_socket |
+ util.HAS_SNI = HAS_SNI |
+ |
+ |
+def extract_from_urllib3(): |
+ 'Undo monkey-patching by :func:`inject_into_urllib3`.' |
+ |
+ connectionpool.ssl_wrap_socket = orig_connectionpool_ssl_wrap_socket |
+ util.HAS_SNI = orig_util_HAS_SNI |
+ |
+ |
+### Note: This is a slightly bug-fixed version of same from ndg-httpsclient. |
+def get_subj_alt_name(peer_cert): |
+ # Search through extensions |
+ dns_name = [] |
+ if not SUBJ_ALT_NAME_SUPPORT: |
+ return dns_name |
+ |
+ general_names = SubjectAltName() |
+ for i in range(peer_cert.get_extension_count()): |
+ ext = peer_cert.get_extension(i) |
+ ext_name = ext.get_short_name() |
+ if ext_name != 'subjectAltName': |
+ continue |
+ |
+ # PyOpenSSL returns extension data in ASN.1 encoded form |
+ ext_dat = ext.get_data() |
+ decoded_dat = der_decoder.decode(ext_dat, |
+ asn1Spec=general_names) |
+ |
+ for name in decoded_dat: |
+ if not isinstance(name, SubjectAltName): |
+ continue |
+ for entry in range(len(name)): |
+ component = name.getComponentByPosition(entry) |
+ if component.getName() != 'dNSName': |
+ continue |
+ dns_name.append(str(component.getComponent())) |
+ |
+ return dns_name |
+ |
+ |
+class WrappedSocket(object): |
+ '''API-compatibility wrapper for Python OpenSSL's Connection-class.''' |
+ |
+ def __init__(self, connection, socket): |
+ self.connection = connection |
+ self.socket = socket |
+ |
+ def makefile(self, mode, bufsize=-1): |
+ return _fileobject(self.connection, mode, bufsize) |
+ |
+ def settimeout(self, timeout): |
+ return self.socket.settimeout(timeout) |
+ |
+ def sendall(self, data): |
+ return self.connection.sendall(data) |
+ |
+ def getpeercert(self, binary_form=False): |
+ x509 = self.connection.get_peer_certificate() |
+ if not x509: |
+ raise ssl.SSLError('') |
+ |
+ if binary_form: |
+ return OpenSSL.crypto.dump_certificate( |
+ OpenSSL.crypto.FILETYPE_ASN1, |
+ x509) |
+ |
+ return { |
+ 'subject': ( |
+ (('commonName', x509.get_subject().CN),), |
+ ), |
+ 'subjectAltName': [ |
+ ('DNS', value) |
+ for value in get_subj_alt_name(x509) |
+ ] |
+ } |
+ |
+ |
+def _verify_callback(cnx, x509, err_no, err_depth, return_code): |
+ return err_no == 0 |
+ |
+ |
+def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, |
+ ca_certs=None, server_hostname=None, |
+ ssl_version=None): |
+ ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version]) |
+ if certfile: |
+ ctx.use_certificate_file(certfile) |
+ if keyfile: |
+ ctx.use_privatekey_file(keyfile) |
+ if cert_reqs != ssl.CERT_NONE: |
+ ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback) |
+ if ca_certs: |
+ try: |
+ ctx.load_verify_locations(ca_certs, None) |
+ except OpenSSL.SSL.Error as e: |
+ raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) |
+ |
+ cnx = OpenSSL.SSL.Connection(ctx, sock) |
+ cnx.set_tlsext_host_name(server_hostname) |
+ cnx.set_connect_state() |
+ try: |
+ cnx.do_handshake() |
+ except OpenSSL.SSL.Error as e: |
+ raise ssl.SSLError('bad handshake', e) |
+ |
+ return WrappedSocket(cnx, sock) |