OLD | NEW |
(Empty) | |
| 1 from __future__ import absolute_import |
| 2 import errno |
| 3 import warnings |
| 4 import hmac |
| 5 |
| 6 from binascii import hexlify, unhexlify |
| 7 from hashlib import md5, sha1, sha256 |
| 8 |
| 9 from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning |
| 10 |
| 11 |
| 12 SSLContext = None |
| 13 HAS_SNI = False |
| 14 IS_PYOPENSSL = False |
| 15 |
| 16 # Maps the length of a digest to a possible hash function producing this digest |
| 17 HASHFUNC_MAP = { |
| 18 32: md5, |
| 19 40: sha1, |
| 20 64: sha256, |
| 21 } |
| 22 |
| 23 |
| 24 def _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 |
| 41 try: # 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? |
| 45 except ImportError: |
| 46 pass |
| 47 |
| 48 |
| 49 try: |
| 50 from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION |
| 51 except 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. |
| 69 DEFAULT_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 |
| 85 try: |
| 86 from ssl import SSLContext # Modern SSL? |
| 87 except 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 |
| 148 def 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 |
| 175 def 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 |
| 198 def 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 |
| 214 def 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: Pyt
hon 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-specifi
c: 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 |
| 277 def 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) |
OLD | NEW |