1 from __future__ import absolute_import
6 from binascii import hexlify, unhexlify
7 from hashlib import md5, sha1, sha256
9 from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
14 create_default_context = None
17 # Maps the length of a digest to a possible hash function producing this digest
25 def _const_compare_digest_backport(a, b):
27 Compare two digests of equal length in constant time.
29 The digests must be of type str/bytes.
30 Returns True if the digests match, and False otherwise.
32 result = abs(len(a) - len(b))
33 for l, r in zip(bytearray(a), bytearray(b)):
38 _const_compare_digest = getattr(hmac, 'compare_digest',
39 _const_compare_digest_backport)
42 try: # Test for SSL features
44 from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
45 from ssl import HAS_SNI # Has SNI?
51 from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION
53 OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
54 OP_NO_COMPRESSION = 0x20000
57 # Sources for more information on TLS ciphers:
59 # - https://wiki.mozilla.org/Security/Server_Side_TLS
60 # - https://www.ssllabs.com/projects/best-practices/index.html
61 # - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
63 # The general intent is:
64 # - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
65 # - prefer ECDHE over DHE for better performance,
66 # - prefer any AES-GCM over any AES-CBC for better performance and security,
67 # - use 3DES as fallback which is secure but slow,
68 # - disable NULL authentication, MD5 MACs and DSS for security reasons.
70 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
71 'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
76 from ssl import SSLContext # Modern SSL?
80 class SSLContext(object): # Platform-specific: Python 2 & 3.1
81 supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or
82 (3, 2) <= sys.version_info)
84 def __init__(self, protocol_version):
85 self.protocol = protocol_version
86 # Use default values from a real SSLContext
87 self.check_hostname = False
88 self.verify_mode = ssl.CERT_NONE
95 def load_cert_chain(self, certfile, keyfile):
96 self.certfile = certfile
97 self.keyfile = keyfile
99 def load_verify_locations(self, cafile=None, capath=None):
100 self.ca_certs = cafile
102 if capath is not None:
103 raise SSLError("CA directories not supported in older Pythons")
105 def set_ciphers(self, cipher_suite):
106 if not self.supports_set_ciphers:
108 'Your version of Python does not support setting '
109 'a custom cipher suite. Please upgrade to Python '
110 '2.7, 3.2, or later if you need this functionality.'
112 self.ciphers = cipher_suite
114 def wrap_socket(self, socket, server_hostname=None, server_side=False):
116 'A true SSLContext object is not available. This prevents '
117 'urllib3 from configuring SSL appropriately and may cause '
118 'certain SSL connections to fail. You can upgrade to a newer '
119 'version of Python to solve this. For more information, see '
120 'https://urllib3.readthedocs.org/en/latest/security.html'
121 '#insecureplatformwarning.',
122 InsecurePlatformWarning
125 'keyfile': self.keyfile,
126 'certfile': self.certfile,
127 'ca_certs': self.ca_certs,
128 'cert_reqs': self.verify_mode,
129 'ssl_version': self.protocol,
130 'server_side': server_side,
132 if self.supports_set_ciphers: # Platform-specific: Python 2.7+
133 return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
134 else: # Platform-specific: Python 2.6
135 return wrap_socket(socket, **kwargs)
138 def assert_fingerprint(cert, fingerprint):
140 Checks if given fingerprint matches the supplied certificate.
143 Certificate as bytes object.
145 Fingerprint as string of hexdigits, can be interspersed by colons.
148 fingerprint = fingerprint.replace(':', '').lower()
149 digest_length = len(fingerprint)
150 hashfunc = HASHFUNC_MAP.get(digest_length)
153 'Fingerprint of invalid length: {0}'.format(fingerprint))
155 # We need encode() here for py32; works on py2 and p33.
156 fingerprint_bytes = unhexlify(fingerprint.encode())
158 cert_digest = hashfunc(cert).digest()
160 if not _const_compare_digest(cert_digest, fingerprint_bytes):
161 raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
162 .format(fingerprint, hexlify(cert_digest)))
165 def resolve_cert_reqs(candidate):
167 Resolves the argument to a numeric constant, which can be passed to
168 the wrap_socket function/method from the ssl module.
169 Defaults to :data:`ssl.CERT_NONE`.
170 If given a string it is assumed to be the name of the constant in the
171 :mod:`ssl` module or its abbrevation.
172 (So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
173 If it's neither `None` nor a string we assume it is already the numeric
174 constant which can directly be passed to wrap_socket.
176 if candidate is None:
179 if isinstance(candidate, str):
180 res = getattr(ssl, candidate, None)
182 res = getattr(ssl, 'CERT_' + candidate)
188 def resolve_ssl_version(candidate):
190 like resolve_cert_reqs
192 if candidate is None:
193 return PROTOCOL_SSLv23
195 if isinstance(candidate, str):
196 res = getattr(ssl, candidate, None)
198 res = getattr(ssl, 'PROTOCOL_' + candidate)
204 def create_urllib3_context(ssl_version=None, cert_reqs=None,
205 options=None, ciphers=None):
206 """All arguments have the same meaning as ``ssl_wrap_socket``.
208 By default, this function does a lot of the same work that
209 ``ssl.create_default_context`` does on Python 3.4+. It:
211 - Disables SSLv2, SSLv3, and compression
212 - Sets a restricted set of server ciphers
214 If you wish to enable SSLv3, you can do::
216 from urllib3.util import ssl_
217 context = ssl_.create_urllib3_context()
218 context.options &= ~ssl_.OP_NO_SSLv3
220 You can do the same to enable compression (substituting ``COMPRESSION``
221 for ``SSLv3`` in the last line above).
224 The desired protocol version to use. This will default to
225 PROTOCOL_SSLv23 which will negotiate the highest protocol that both
226 the server and your installation of OpenSSL support.
228 Whether to require the certificate verification. This defaults to
229 ``ssl.CERT_REQUIRED``.
231 Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
232 ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``.
234 Which cipher suites to allow the server to select.
236 Constructed SSLContext object with specified options
239 context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23)
241 # Setting the default here, as we may have no ssl module on import
242 cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
246 # SSLv2 is easily broken and is considered harmful and dangerous
247 options |= OP_NO_SSLv2
248 # SSLv3 has several problems and is now dangerous
249 options |= OP_NO_SSLv3
250 # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
252 options |= OP_NO_COMPRESSION
254 context.options |= options
256 if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6
257 context.set_ciphers(ciphers or DEFAULT_CIPHERS)
259 context.verify_mode = cert_reqs
260 if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2
261 # We do our own verification, including fingerprints and alternative
262 # hostnames. So disable it here
263 context.check_hostname = False
267 def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
268 ca_certs=None, server_hostname=None,
269 ssl_version=None, ciphers=None, ssl_context=None,
272 All arguments except for server_hostname, ssl_context, and ca_cert_dir have
273 the same meaning as they do when using :func:`ssl.wrap_socket`.
275 :param server_hostname:
276 When SNI is supported, the expected hostname of the certificate
278 A pre-made :class:`SSLContext` object. If none is provided, one will
279 be created using :func:`create_urllib3_context`.
281 A string of ciphers we wish the client to support. This is not
282 supported on Python 2.6 as the ssl module does not support it.
284 A directory containing CA certificates in multiple separate files, as
285 supported by OpenSSL's -CApath flag or the capath argument to
286 SSLContext.load_verify_locations().
288 context = ssl_context
290 context = create_urllib3_context(ssl_version, cert_reqs,
293 if ca_certs or ca_cert_dir:
295 context.load_verify_locations(ca_certs, ca_cert_dir)
296 except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2
298 # Py33 raises FileNotFoundError which subclasses OSError
299 # These are not equivalent unless we check the errno attribute
300 except OSError as e: # Platform-specific: Python 3.3 and beyond
301 if e.errno == errno.ENOENT:
306 context.load_cert_chain(certfile, keyfile)
307 if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
308 return context.wrap_socket(sock, server_hostname=server_hostname)
311 'An HTTPS request has been made, but the SNI (Subject Name '
312 'Indication) extension to TLS is not available on this platform. '
313 'This may cause the server to present an incorrect TLS '
314 'certificate, which can cause validation failures. You can upgrade to '
315 'a newer version of Python to solve this. For more information, see '
316 'https://urllib3.readthedocs.org/en/latest/security.html'
317 '#snimissingwarning.',
320 return context.wrap_socket(sock)