Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/urllib3/util/ssl_.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

194 statements  

1from __future__ import absolute_import 

2 

3import hashlib 

4import hmac 

5import os 

6import sys 

7import warnings 

8from binascii import hexlify, unhexlify 

9 

10from ..exceptions import ( 

11 InsecurePlatformWarning, 

12 ProxySchemeUnsupported, 

13 SNIMissingWarning, 

14 SSLError, 

15) 

16from ..packages import six 

17from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE 

18 

19SSLContext = None 

20SSLTransport = None 

21HAS_SNI = False 

22IS_PYOPENSSL = False 

23IS_SECURETRANSPORT = False 

24ALPN_PROTOCOLS = ["http/1.1"] 

25 

26# Maps the length of a digest to a possible hash function producing this digest 

27HASHFUNC_MAP = { 

28 length: getattr(hashlib, algorithm, None) 

29 for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256")) 

30} 

31 

32 

33def _const_compare_digest_backport(a, b): 

34 """ 

35 Compare two digests of equal length in constant time. 

36 

37 The digests must be of type str/bytes. 

38 Returns True if the digests match, and False otherwise. 

39 """ 

40 result = abs(len(a) - len(b)) 

41 for left, right in zip(bytearray(a), bytearray(b)): 

42 result |= left ^ right 

43 return result == 0 

44 

45 

46_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) 

47 

48try: # Test for SSL features 

49 import ssl 

50 from ssl import CERT_REQUIRED, wrap_socket 

51except ImportError: 

52 pass 

53 

54try: 

55 from ssl import HAS_SNI # Has SNI? 

56except ImportError: 

57 pass 

58 

59try: 

60 from .ssltransport import SSLTransport 

61except ImportError: 

62 pass 

63 

64 

65try: # Platform-specific: Python 3.6 

66 from ssl import PROTOCOL_TLS 

67 

68 PROTOCOL_SSLv23 = PROTOCOL_TLS 

69except ImportError: 

70 try: 

71 from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS 

72 

73 PROTOCOL_SSLv23 = PROTOCOL_TLS 

74 except ImportError: 

75 PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 

76 

77try: 

78 from ssl import PROTOCOL_TLS_CLIENT 

79except ImportError: 

80 PROTOCOL_TLS_CLIENT = PROTOCOL_TLS 

81 

82 

83try: 

84 from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 

85except ImportError: 

86 OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 

87 OP_NO_COMPRESSION = 0x20000 

88 

89 

90try: # OP_NO_TICKET was added in Python 3.6 

91 from ssl import OP_NO_TICKET 

92except ImportError: 

93 OP_NO_TICKET = 0x4000 

94 

95 

96# A secure default. 

97# Sources for more information on TLS ciphers: 

98# 

99# - https://wiki.mozilla.org/Security/Server_Side_TLS 

100# - https://www.ssllabs.com/projects/best-practices/index.html 

101# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ 

102# 

103# The general intent is: 

104# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), 

105# - prefer ECDHE over DHE for better performance, 

106# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and 

107# security, 

108# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, 

109# - disable NULL authentication, MD5 MACs, DSS, and other 

110# insecure ciphers for security reasons. 

111# - NOTE: TLS 1.3 cipher suites are managed through a different interface 

112# not exposed by CPython (yet!) and are enabled by default if they're available. 

113DEFAULT_CIPHERS = ":".join( 

114 [ 

115 "ECDHE+AESGCM", 

116 "ECDHE+CHACHA20", 

117 "DHE+AESGCM", 

118 "DHE+CHACHA20", 

119 "ECDH+AESGCM", 

120 "DH+AESGCM", 

121 "ECDH+AES", 

122 "DH+AES", 

123 "RSA+AESGCM", 

124 "RSA+AES", 

125 "!aNULL", 

126 "!eNULL", 

127 "!MD5", 

128 "!DSS", 

129 ] 

130) 

131 

132try: 

133 from ssl import SSLContext # Modern SSL? 

134except ImportError: 

135 

136 class SSLContext(object): # Platform-specific: Python 2 

137 def __init__(self, protocol_version): 

138 self.protocol = protocol_version 

139 # Use default values from a real SSLContext 

140 self.check_hostname = False 

141 self.verify_mode = ssl.CERT_NONE 

142 self.ca_certs = None 

143 self.options = 0 

144 self.certfile = None 

145 self.keyfile = None 

146 self.ciphers = None 

147 

148 def load_cert_chain(self, certfile, keyfile): 

149 self.certfile = certfile 

150 self.keyfile = keyfile 

151 

152 def load_verify_locations(self, cafile=None, capath=None, cadata=None): 

153 self.ca_certs = cafile 

154 

155 if capath is not None: 

156 raise SSLError("CA directories not supported in older Pythons") 

157 

158 if cadata is not None: 

159 raise SSLError("CA data not supported in older Pythons") 

160 

161 def set_ciphers(self, cipher_suite): 

162 self.ciphers = cipher_suite 

163 

164 def wrap_socket(self, socket, server_hostname=None, server_side=False): 

165 warnings.warn( 

166 "A true SSLContext object is not available. This prevents " 

167 "urllib3 from configuring SSL appropriately and may cause " 

168 "certain SSL connections to fail. You can upgrade to a newer " 

169 "version of Python to solve this. For more information, see " 

170 "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" 

171 "#ssl-warnings", 

172 InsecurePlatformWarning, 

173 ) 

174 kwargs = { 

175 "keyfile": self.keyfile, 

176 "certfile": self.certfile, 

177 "ca_certs": self.ca_certs, 

178 "cert_reqs": self.verify_mode, 

179 "ssl_version": self.protocol, 

180 "server_side": server_side, 

181 } 

182 return wrap_socket(socket, ciphers=self.ciphers, **kwargs) 

183 

184 

185def assert_fingerprint(cert, fingerprint): 

186 """ 

187 Checks if given fingerprint matches the supplied certificate. 

188 

189 :param cert: 

190 Certificate as bytes object. 

191 :param fingerprint: 

192 Fingerprint as string of hexdigits, can be interspersed by colons. 

193 """ 

194 

195 fingerprint = fingerprint.replace(":", "").lower() 

196 digest_length = len(fingerprint) 

197 if digest_length not in HASHFUNC_MAP: 

198 raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) 

199 hashfunc = HASHFUNC_MAP.get(digest_length) 

200 if hashfunc is None: 

201 raise SSLError( 

202 "Hash function implementation unavailable for fingerprint length: {0}".format( 

203 digest_length 

204 ) 

205 ) 

206 

207 # We need encode() here for py32; works on py2 and p33. 

208 fingerprint_bytes = unhexlify(fingerprint.encode()) 

209 

210 cert_digest = hashfunc(cert).digest() 

211 

212 if not _const_compare_digest(cert_digest, fingerprint_bytes): 

213 raise SSLError( 

214 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( 

215 fingerprint, hexlify(cert_digest) 

216 ) 

217 ) 

218 

219 

220def resolve_cert_reqs(candidate): 

221 """ 

222 Resolves the argument to a numeric constant, which can be passed to 

223 the wrap_socket function/method from the ssl module. 

224 Defaults to :data:`ssl.CERT_REQUIRED`. 

225 If given a string it is assumed to be the name of the constant in the 

226 :mod:`ssl` module or its abbreviation. 

227 (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. 

228 If it's neither `None` nor a string we assume it is already the numeric 

229 constant which can directly be passed to wrap_socket. 

230 """ 

231 if candidate is None: 

232 return CERT_REQUIRED 

233 

234 if isinstance(candidate, str): 

235 res = getattr(ssl, candidate, None) 

236 if res is None: 

237 res = getattr(ssl, "CERT_" + candidate) 

238 return res 

239 

240 return candidate 

241 

242 

243def resolve_ssl_version(candidate): 

244 """ 

245 like resolve_cert_reqs 

246 """ 

247 if candidate is None: 

248 return PROTOCOL_TLS 

249 

250 if isinstance(candidate, str): 

251 res = getattr(ssl, candidate, None) 

252 if res is None: 

253 res = getattr(ssl, "PROTOCOL_" + candidate) 

254 return res 

255 

256 return candidate 

257 

258 

259def create_urllib3_context( 

260 ssl_version=None, cert_reqs=None, options=None, ciphers=None 

261): 

262 """All arguments have the same meaning as ``ssl_wrap_socket``. 

263 

264 By default, this function does a lot of the same work that 

265 ``ssl.create_default_context`` does on Python 3.4+. It: 

266 

267 - Disables SSLv2, SSLv3, and compression 

268 - Sets a restricted set of server ciphers 

269 

270 If you wish to enable SSLv3, you can do:: 

271 

272 from urllib3.util import ssl_ 

273 context = ssl_.create_urllib3_context() 

274 context.options &= ~ssl_.OP_NO_SSLv3 

275 

276 You can do the same to enable compression (substituting ``COMPRESSION`` 

277 for ``SSLv3`` in the last line above). 

278 

279 :param ssl_version: 

280 The desired protocol version to use. This will default to 

281 PROTOCOL_SSLv23 which will negotiate the highest protocol that both 

282 the server and your installation of OpenSSL support. 

283 :param cert_reqs: 

284 Whether to require the certificate verification. This defaults to 

285 ``ssl.CERT_REQUIRED``. 

286 :param options: 

287 Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, 

288 ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. 

289 :param ciphers: 

290 Which cipher suites to allow the server to select. 

291 :returns: 

292 Constructed SSLContext object with specified options 

293 :rtype: SSLContext 

294 """ 

295 # PROTOCOL_TLS is deprecated in Python 3.10 

296 if not ssl_version or ssl_version == PROTOCOL_TLS: 

297 ssl_version = PROTOCOL_TLS_CLIENT 

298 

299 context = SSLContext(ssl_version) 

300 

301 context.set_ciphers(ciphers or DEFAULT_CIPHERS) 

302 

303 # Setting the default here, as we may have no ssl module on import 

304 cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs 

305 

306 if options is None: 

307 options = 0 

308 # SSLv2 is easily broken and is considered harmful and dangerous 

309 options |= OP_NO_SSLv2 

310 # SSLv3 has several problems and is now dangerous 

311 options |= OP_NO_SSLv3 

312 # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ 

313 # (issue #309) 

314 options |= OP_NO_COMPRESSION 

315 # TLSv1.2 only. Unless set explicitly, do not request tickets. 

316 # This may save some bandwidth on wire, and although the ticket is encrypted, 

317 # there is a risk associated with it being on wire, 

318 # if the server is not rotating its ticketing keys properly. 

319 options |= OP_NO_TICKET 

320 

321 context.options |= options 

322 

323 # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is 

324 # necessary for conditional client cert authentication with TLS 1.3. 

325 # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older 

326 # versions of Python. We only enable on Python 3.7.4+ or if certificate 

327 # verification is enabled to work around Python issue #37428 

328 # See: https://bugs.python.org/issue37428 

329 if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( 

330 context, "post_handshake_auth", None 

331 ) is not None: 

332 context.post_handshake_auth = True 

333 

334 def disable_check_hostname(): 

335 if ( 

336 getattr(context, "check_hostname", None) is not None 

337 ): # Platform-specific: Python 3.2 

338 # We do our own verification, including fingerprints and alternative 

339 # hostnames. So disable it here 

340 context.check_hostname = False 

341 

342 # The order of the below lines setting verify_mode and check_hostname 

343 # matter due to safe-guards SSLContext has to prevent an SSLContext with 

344 # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more 

345 # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used 

346 # or not so we don't know the initial state of the freshly created SSLContext. 

347 if cert_reqs == ssl.CERT_REQUIRED: 

348 context.verify_mode = cert_reqs 

349 disable_check_hostname() 

350 else: 

351 disable_check_hostname() 

352 context.verify_mode = cert_reqs 

353 

354 # Enable logging of TLS session keys via defacto standard environment variable 

355 # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. 

356 if hasattr(context, "keylog_filename"): 

357 sslkeylogfile = os.environ.get("SSLKEYLOGFILE") 

358 if sslkeylogfile: 

359 context.keylog_filename = sslkeylogfile 

360 

361 return context 

362 

363 

364def ssl_wrap_socket( 

365 sock, 

366 keyfile=None, 

367 certfile=None, 

368 cert_reqs=None, 

369 ca_certs=None, 

370 server_hostname=None, 

371 ssl_version=None, 

372 ciphers=None, 

373 ssl_context=None, 

374 ca_cert_dir=None, 

375 key_password=None, 

376 ca_cert_data=None, 

377 tls_in_tls=False, 

378): 

379 """ 

380 All arguments except for server_hostname, ssl_context, and ca_cert_dir have 

381 the same meaning as they do when using :func:`ssl.wrap_socket`. 

382 

383 :param server_hostname: 

384 When SNI is supported, the expected hostname of the certificate 

385 :param ssl_context: 

386 A pre-made :class:`SSLContext` object. If none is provided, one will 

387 be created using :func:`create_urllib3_context`. 

388 :param ciphers: 

389 A string of ciphers we wish the client to support. 

390 :param ca_cert_dir: 

391 A directory containing CA certificates in multiple separate files, as 

392 supported by OpenSSL's -CApath flag or the capath argument to 

393 SSLContext.load_verify_locations(). 

394 :param key_password: 

395 Optional password if the keyfile is encrypted. 

396 :param ca_cert_data: 

397 Optional string containing CA certificates in PEM format suitable for 

398 passing as the cadata parameter to SSLContext.load_verify_locations() 

399 :param tls_in_tls: 

400 Use SSLTransport to wrap the existing socket. 

401 """ 

402 context = ssl_context 

403 if context is None: 

404 # Note: This branch of code and all the variables in it are no longer 

405 # used by urllib3 itself. We should consider deprecating and removing 

406 # this code. 

407 context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) 

408 

409 if ca_certs or ca_cert_dir or ca_cert_data: 

410 try: 

411 context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) 

412 except (IOError, OSError) as e: 

413 raise SSLError(e) 

414 

415 elif ssl_context is None and hasattr(context, "load_default_certs"): 

416 # try to load OS default certs; works well on Windows (require Python3.4+) 

417 context.load_default_certs() 

418 

419 # Attempt to detect if we get the goofy behavior of the 

420 # keyfile being encrypted and OpenSSL asking for the 

421 # passphrase via the terminal and instead error out. 

422 if keyfile and key_password is None and _is_key_file_encrypted(keyfile): 

423 raise SSLError("Client private key is encrypted, password is required") 

424 

425 if certfile: 

426 if key_password is None: 

427 context.load_cert_chain(certfile, keyfile) 

428 else: 

429 context.load_cert_chain(certfile, keyfile, key_password) 

430 

431 try: 

432 if hasattr(context, "set_alpn_protocols"): 

433 context.set_alpn_protocols(ALPN_PROTOCOLS) 

434 except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols 

435 pass 

436 

437 # If we detect server_hostname is an IP address then the SNI 

438 # extension should not be used according to RFC3546 Section 3.1 

439 use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) 

440 # SecureTransport uses server_hostname in certificate verification. 

441 send_sni = (use_sni_hostname and HAS_SNI) or ( 

442 IS_SECURETRANSPORT and server_hostname 

443 ) 

444 # Do not warn the user if server_hostname is an invalid SNI hostname. 

445 if not HAS_SNI and use_sni_hostname: 

446 warnings.warn( 

447 "An HTTPS request has been made, but the SNI (Server Name " 

448 "Indication) extension to TLS is not available on this platform. " 

449 "This may cause the server to present an incorrect TLS " 

450 "certificate, which can cause validation failures. You can upgrade to " 

451 "a newer version of Python to solve this. For more information, see " 

452 "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" 

453 "#ssl-warnings", 

454 SNIMissingWarning, 

455 ) 

456 

457 if send_sni: 

458 ssl_sock = _ssl_wrap_socket_impl( 

459 sock, context, tls_in_tls, server_hostname=server_hostname 

460 ) 

461 else: 

462 ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) 

463 return ssl_sock 

464 

465 

466def is_ipaddress(hostname): 

467 """Detects whether the hostname given is an IPv4 or IPv6 address. 

468 Also detects IPv6 addresses with Zone IDs. 

469 

470 :param str hostname: Hostname to examine. 

471 :return: True if the hostname is an IP address, False otherwise. 

472 """ 

473 if not six.PY2 and isinstance(hostname, bytes): 

474 # IDN A-label bytes are ASCII compatible. 

475 hostname = hostname.decode("ascii") 

476 return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) 

477 

478 

479def _is_key_file_encrypted(key_file): 

480 """Detects if a key file is encrypted or not.""" 

481 with open(key_file, "r") as f: 

482 for line in f: 

483 # Look for Proc-Type: 4,ENCRYPTED 

484 if "ENCRYPTED" in line: 

485 return True 

486 

487 return False 

488 

489 

490def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): 

491 if tls_in_tls: 

492 if not SSLTransport: 

493 # Import error, ssl is not available. 

494 raise ProxySchemeUnsupported( 

495 "TLS in TLS requires support for the 'ssl' module" 

496 ) 

497 

498 SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) 

499 return SSLTransport(sock, ssl_context, server_hostname) 

500 

501 if server_hostname: 

502 return ssl_context.wrap_socket(sock, server_hostname=server_hostname) 

503 else: 

504 return ssl_context.wrap_socket(sock)