1from __future__ import absolute_import 2import errno 3import warnings 4import hmac 5 6from binascii import hexlify, unhexlify 7from hashlib import md5, sha1, sha256 8 9from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning 10 11 12SSLContext = None 13HAS_SNI = False 14IS_PYOPENSSL = False 15 16# Maps the length of a digest to a possible hash function producing this digest 17HASHFUNC_MAP = { 18 32: md5, 19 40: sha1, 20 64: sha256, 21} 22 23 24def _const_compare_digest_backport(a, b): 25 """ 26 Compare two digests of equal length in constant time. 27 28 The digests must be of type str/bytes. 29 Returns True if the digests match, and False otherwise. 30 """ 31 result = abs(len(a) - len(b)) 32 for l, r in zip(bytearray(a), bytearray(b)): 33 result |= l ^ r 34 return result == 0 35 36 37_const_compare_digest = getattr(hmac, 'compare_digest', 38 _const_compare_digest_backport) 39 40 41try: # Test for SSL features 42 import ssl 43 from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 44 from ssl import HAS_SNI # Has SNI? 45except ImportError: 46 pass 47 48 49try: 50 from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION 51except ImportError: 52 OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 53 OP_NO_COMPRESSION = 0x20000 54 55# A secure default. 56# Sources for more information on TLS ciphers: 57# 58# - https://wiki.mozilla.org/Security/Server_Side_TLS 59# - https://www.ssllabs.com/projects/best-practices/index.html 60# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ 61# 62# The general intent is: 63# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), 64# - prefer ECDHE over DHE for better performance, 65# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and 66# security, 67# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, 68# - disable NULL authentication, MD5 MACs and DSS for security reasons. 69DEFAULT_CIPHERS = ':'.join([ 70 'ECDH+AESGCM', 71 'ECDH+CHACHA20', 72 'DH+AESGCM', 73 'DH+CHACHA20', 74 'ECDH+AES256', 75 'DH+AES256', 76 'ECDH+AES128', 77 'DH+AES', 78 'RSA+AESGCM', 79 'RSA+AES', 80 '!aNULL', 81 '!eNULL', 82 '!MD5', 83]) 84 85try: 86 from ssl import SSLContext # Modern SSL? 87except ImportError: 88 import sys 89 90 class SSLContext(object): # Platform-specific: Python 2 & 3.1 91 supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or 92 (3, 2) <= sys.version_info) 93 94 def __init__(self, protocol_version): 95 self.protocol = protocol_version 96 # Use default values from a real SSLContext 97 self.check_hostname = False 98 self.verify_mode = ssl.CERT_NONE 99 self.ca_certs = None 100 self.options = 0 101 self.certfile = None 102 self.keyfile = None 103 self.ciphers = None 104 105 def load_cert_chain(self, certfile, keyfile): 106 self.certfile = certfile 107 self.keyfile = keyfile 108 109 def load_verify_locations(self, cafile=None, capath=None): 110 self.ca_certs = cafile 111 112 if capath is not None: 113 raise SSLError("CA directories not supported in older Pythons") 114 115 def set_ciphers(self, cipher_suite): 116 if not self.supports_set_ciphers: 117 raise TypeError( 118 'Your version of Python does not support setting ' 119 'a custom cipher suite. Please upgrade to Python ' 120 '2.7, 3.2, or later if you need this functionality.' 121 ) 122 self.ciphers = cipher_suite 123 124 def wrap_socket(self, socket, server_hostname=None, server_side=False): 125 warnings.warn( 126 'A true SSLContext object is not available. This prevents ' 127 'urllib3 from configuring SSL appropriately and may cause ' 128 'certain SSL connections to fail. You can upgrade to a newer ' 129 'version of Python to solve this. For more information, see ' 130 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' 131 '#ssl-warnings', 132 InsecurePlatformWarning 133 ) 134 kwargs = { 135 'keyfile': self.keyfile, 136 'certfile': self.certfile, 137 'ca_certs': self.ca_certs, 138 'cert_reqs': self.verify_mode, 139 'ssl_version': self.protocol, 140 'server_side': server_side, 141 } 142 if self.supports_set_ciphers: # Platform-specific: Python 2.7+ 143 return wrap_socket(socket, ciphers=self.ciphers, **kwargs) 144 else: # Platform-specific: Python 2.6 145 return wrap_socket(socket, **kwargs) 146 147 148def assert_fingerprint(cert, fingerprint): 149 """ 150 Checks if given fingerprint matches the supplied certificate. 151 152 :param cert: 153 Certificate as bytes object. 154 :param fingerprint: 155 Fingerprint as string of hexdigits, can be interspersed by colons. 156 """ 157 158 fingerprint = fingerprint.replace(':', '').lower() 159 digest_length = len(fingerprint) 160 hashfunc = HASHFUNC_MAP.get(digest_length) 161 if not hashfunc: 162 raise SSLError( 163 'Fingerprint of invalid length: {0}'.format(fingerprint)) 164 165 # We need encode() here for py32; works on py2 and p33. 166 fingerprint_bytes = unhexlify(fingerprint.encode()) 167 168 cert_digest = hashfunc(cert).digest() 169 170 if not _const_compare_digest(cert_digest, fingerprint_bytes): 171 raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' 172 .format(fingerprint, hexlify(cert_digest))) 173 174 175def resolve_cert_reqs(candidate): 176 """ 177 Resolves the argument to a numeric constant, which can be passed to 178 the wrap_socket function/method from the ssl module. 179 Defaults to :data:`ssl.CERT_NONE`. 180 If given a string it is assumed to be the name of the constant in the 181 :mod:`ssl` module or its abbrevation. 182 (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. 183 If it's neither `None` nor a string we assume it is already the numeric 184 constant which can directly be passed to wrap_socket. 185 """ 186 if candidate is None: 187 return CERT_NONE 188 189 if isinstance(candidate, str): 190 res = getattr(ssl, candidate, None) 191 if res is None: 192 res = getattr(ssl, 'CERT_' + candidate) 193 return res 194 195 return candidate 196 197 198def resolve_ssl_version(candidate): 199 """ 200 like resolve_cert_reqs 201 """ 202 if candidate is None: 203 return PROTOCOL_SSLv23 204 205 if isinstance(candidate, str): 206 res = getattr(ssl, candidate, None) 207 if res is None: 208 res = getattr(ssl, 'PROTOCOL_' + candidate) 209 return res 210 211 return candidate 212 213 214def create_urllib3_context(ssl_version=None, cert_reqs=None, 215 options=None, ciphers=None): 216 """All arguments have the same meaning as ``ssl_wrap_socket``. 217 218 By default, this function does a lot of the same work that 219 ``ssl.create_default_context`` does on Python 3.4+. It: 220 221 - Disables SSLv2, SSLv3, and compression 222 - Sets a restricted set of server ciphers 223 224 If you wish to enable SSLv3, you can do:: 225 226 from urllib3.util import ssl_ 227 context = ssl_.create_urllib3_context() 228 context.options &= ~ssl_.OP_NO_SSLv3 229 230 You can do the same to enable compression (substituting ``COMPRESSION`` 231 for ``SSLv3`` in the last line above). 232 233 :param ssl_version: 234 The desired protocol version to use. This will default to 235 PROTOCOL_SSLv23 which will negotiate the highest protocol that both 236 the server and your installation of OpenSSL support. 237 :param cert_reqs: 238 Whether to require the certificate verification. This defaults to 239 ``ssl.CERT_REQUIRED``. 240 :param options: 241 Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, 242 ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. 243 :param ciphers: 244 Which cipher suites to allow the server to select. 245 :returns: 246 Constructed SSLContext object with specified options 247 :rtype: SSLContext 248 """ 249 context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) 250 251 # Setting the default here, as we may have no ssl module on import 252 cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs 253 254 if options is None: 255 options = 0 256 # SSLv2 is easily broken and is considered harmful and dangerous 257 options |= OP_NO_SSLv2 258 # SSLv3 has several problems and is now dangerous 259 options |= OP_NO_SSLv3 260 # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ 261 # (issue #309) 262 options |= OP_NO_COMPRESSION 263 264 context.options |= options 265 266 if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6 267 context.set_ciphers(ciphers or DEFAULT_CIPHERS) 268 269 context.verify_mode = cert_reqs 270 if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 271 # We do our own verification, including fingerprints and alternative 272 # hostnames. So disable it here 273 context.check_hostname = False 274 return context 275 276 277def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, 278 ca_certs=None, server_hostname=None, 279 ssl_version=None, ciphers=None, ssl_context=None, 280 ca_cert_dir=None): 281 """ 282 All arguments except for server_hostname, ssl_context, and ca_cert_dir have 283 the same meaning as they do when using :func:`ssl.wrap_socket`. 284 285 :param server_hostname: 286 When SNI is supported, the expected hostname of the certificate 287 :param ssl_context: 288 A pre-made :class:`SSLContext` object. If none is provided, one will 289 be created using :func:`create_urllib3_context`. 290 :param ciphers: 291 A string of ciphers we wish the client to support. This is not 292 supported on Python 2.6 as the ssl module does not support it. 293 :param ca_cert_dir: 294 A directory containing CA certificates in multiple separate files, as 295 supported by OpenSSL's -CApath flag or the capath argument to 296 SSLContext.load_verify_locations(). 297 """ 298 context = ssl_context 299 if context is None: 300 # Note: This branch of code and all the variables in it are no longer 301 # used by urllib3 itself. We should consider deprecating and removing 302 # this code. 303 context = create_urllib3_context(ssl_version, cert_reqs, 304 ciphers=ciphers) 305 306 if ca_certs or ca_cert_dir: 307 try: 308 context.load_verify_locations(ca_certs, ca_cert_dir) 309 except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2 310 raise SSLError(e) 311 # Py33 raises FileNotFoundError which subclasses OSError 312 # These are not equivalent unless we check the errno attribute 313 except OSError as e: # Platform-specific: Python 3.3 and beyond 314 if e.errno == errno.ENOENT: 315 raise SSLError(e) 316 raise 317 elif getattr(context, 'load_default_certs', None) is not None: 318 # try to load OS default certs; works well on Windows (require Python3.4+) 319 context.load_default_certs() 320 321 if certfile: 322 context.load_cert_chain(certfile, keyfile) 323 if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI 324 return context.wrap_socket(sock, server_hostname=server_hostname) 325 326 warnings.warn( 327 'An HTTPS request has been made, but the SNI (Subject Name ' 328 'Indication) extension to TLS is not available on this platform. ' 329 'This may cause the server to present an incorrect TLS ' 330 'certificate, which can cause validation failures. You can upgrade to ' 331 'a newer version of Python to solve this. For more information, see ' 332 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' 333 '#ssl-warnings', 334 SNIMissingWarning 335 ) 336 return context.wrap_socket(sock) 337