comparison planemo/lib/python3.7/site-packages/urllib3/contrib/securetransport.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 """
2 SecureTranport support for urllib3 via ctypes.
3
4 This makes platform-native TLS available to urllib3 users on macOS without the
5 use of a compiler. This is an important feature because the Python Package
6 Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL
7 that ships with macOS is not capable of doing TLSv1.2. The only way to resolve
8 this is to give macOS users an alternative solution to the problem, and that
9 solution is to use SecureTransport.
10
11 We use ctypes here because this solution must not require a compiler. That's
12 because pip is not allowed to require a compiler either.
13
14 This is not intended to be a seriously long-term solution to this problem.
15 The hope is that PEP 543 will eventually solve this issue for us, at which
16 point we can retire this contrib module. But in the short term, we need to
17 solve the impending tire fire that is Python on Mac without this kind of
18 contrib module. So...here we are.
19
20 To use this module, simply import and inject it::
21
22 import urllib3.contrib.securetransport
23 urllib3.contrib.securetransport.inject_into_urllib3()
24
25 Happy TLSing!
26
27 This code is a bastardised version of the code found in Will Bond's oscrypto
28 library. An enormous debt is owed to him for blazing this trail for us. For
29 that reason, this code should be considered to be covered both by urllib3's
30 license and by oscrypto's:
31
32 Copyright (c) 2015-2016 Will Bond <will@wbond.net>
33
34 Permission is hereby granted, free of charge, to any person obtaining a
35 copy of this software and associated documentation files (the "Software"),
36 to deal in the Software without restriction, including without limitation
37 the rights to use, copy, modify, merge, publish, distribute, sublicense,
38 and/or sell copies of the Software, and to permit persons to whom the
39 Software is furnished to do so, subject to the following conditions:
40
41 The above copyright notice and this permission notice shall be included in
42 all copies or substantial portions of the Software.
43
44 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
49 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
50 DEALINGS IN THE SOFTWARE.
51 """
52 from __future__ import absolute_import
53
54 import contextlib
55 import ctypes
56 import errno
57 import os.path
58 import shutil
59 import socket
60 import ssl
61 import threading
62 import weakref
63
64 from .. import util
65 from ._securetransport.bindings import Security, SecurityConst, CoreFoundation
66 from ._securetransport.low_level import (
67 _assert_no_error,
68 _cert_array_from_pem,
69 _temporary_keychain,
70 _load_client_cert_chain,
71 )
72
73 try: # Platform-specific: Python 2
74 from socket import _fileobject
75 except ImportError: # Platform-specific: Python 3
76 _fileobject = None
77 from ..packages.backports.makefile import backport_makefile
78
79 __all__ = ["inject_into_urllib3", "extract_from_urllib3"]
80
81 # SNI always works
82 HAS_SNI = True
83
84 orig_util_HAS_SNI = util.HAS_SNI
85 orig_util_SSLContext = util.ssl_.SSLContext
86
87 # This dictionary is used by the read callback to obtain a handle to the
88 # calling wrapped socket. This is a pretty silly approach, but for now it'll
89 # do. I feel like I should be able to smuggle a handle to the wrapped socket
90 # directly in the SSLConnectionRef, but for now this approach will work I
91 # guess.
92 #
93 # We need to lock around this structure for inserts, but we don't do it for
94 # reads/writes in the callbacks. The reasoning here goes as follows:
95 #
96 # 1. It is not possible to call into the callbacks before the dictionary is
97 # populated, so once in the callback the id must be in the dictionary.
98 # 2. The callbacks don't mutate the dictionary, they only read from it, and
99 # so cannot conflict with any of the insertions.
100 #
101 # This is good: if we had to lock in the callbacks we'd drastically slow down
102 # the performance of this code.
103 _connection_refs = weakref.WeakValueDictionary()
104 _connection_ref_lock = threading.Lock()
105
106 # Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over
107 # for no better reason than we need *a* limit, and this one is right there.
108 SSL_WRITE_BLOCKSIZE = 16384
109
110 # This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to
111 # individual cipher suites. We need to do this because this is how
112 # SecureTransport wants them.
113 CIPHER_SUITES = [
114 SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
115 SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
116 SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
117 SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
118 SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
119 SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
120 SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
121 SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
122 SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
123 SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
124 SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
125 SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
126 SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
127 SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
128 SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
129 SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
130 SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
131 SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
132 SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
133 SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
134 SecurityConst.TLS_AES_256_GCM_SHA384,
135 SecurityConst.TLS_AES_128_GCM_SHA256,
136 SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384,
137 SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256,
138 SecurityConst.TLS_AES_128_CCM_8_SHA256,
139 SecurityConst.TLS_AES_128_CCM_SHA256,
140 SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256,
141 SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256,
142 SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA,
143 SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA,
144 ]
145
146 # Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of
147 # TLSv1 and a high of TLSv1.2. For everything else, we pin to that version.
148 # TLSv1 to 1.2 are supported on macOS 10.8+
149 _protocol_to_min_max = {
150 util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12)
151 }
152
153 if hasattr(ssl, "PROTOCOL_SSLv2"):
154 _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = (
155 SecurityConst.kSSLProtocol2,
156 SecurityConst.kSSLProtocol2,
157 )
158 if hasattr(ssl, "PROTOCOL_SSLv3"):
159 _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = (
160 SecurityConst.kSSLProtocol3,
161 SecurityConst.kSSLProtocol3,
162 )
163 if hasattr(ssl, "PROTOCOL_TLSv1"):
164 _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = (
165 SecurityConst.kTLSProtocol1,
166 SecurityConst.kTLSProtocol1,
167 )
168 if hasattr(ssl, "PROTOCOL_TLSv1_1"):
169 _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = (
170 SecurityConst.kTLSProtocol11,
171 SecurityConst.kTLSProtocol11,
172 )
173 if hasattr(ssl, "PROTOCOL_TLSv1_2"):
174 _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = (
175 SecurityConst.kTLSProtocol12,
176 SecurityConst.kTLSProtocol12,
177 )
178
179
180 def inject_into_urllib3():
181 """
182 Monkey-patch urllib3 with SecureTransport-backed SSL-support.
183 """
184 util.SSLContext = SecureTransportContext
185 util.ssl_.SSLContext = SecureTransportContext
186 util.HAS_SNI = HAS_SNI
187 util.ssl_.HAS_SNI = HAS_SNI
188 util.IS_SECURETRANSPORT = True
189 util.ssl_.IS_SECURETRANSPORT = True
190
191
192 def extract_from_urllib3():
193 """
194 Undo monkey-patching by :func:`inject_into_urllib3`.
195 """
196 util.SSLContext = orig_util_SSLContext
197 util.ssl_.SSLContext = orig_util_SSLContext
198 util.HAS_SNI = orig_util_HAS_SNI
199 util.ssl_.HAS_SNI = orig_util_HAS_SNI
200 util.IS_SECURETRANSPORT = False
201 util.ssl_.IS_SECURETRANSPORT = False
202
203
204 def _read_callback(connection_id, data_buffer, data_length_pointer):
205 """
206 SecureTransport read callback. This is called by ST to request that data
207 be returned from the socket.
208 """
209 wrapped_socket = None
210 try:
211 wrapped_socket = _connection_refs.get(connection_id)
212 if wrapped_socket is None:
213 return SecurityConst.errSSLInternal
214 base_socket = wrapped_socket.socket
215
216 requested_length = data_length_pointer[0]
217
218 timeout = wrapped_socket.gettimeout()
219 error = None
220 read_count = 0
221
222 try:
223 while read_count < requested_length:
224 if timeout is None or timeout >= 0:
225 if not util.wait_for_read(base_socket, timeout):
226 raise socket.error(errno.EAGAIN, "timed out")
227
228 remaining = requested_length - read_count
229 buffer = (ctypes.c_char * remaining).from_address(
230 data_buffer + read_count
231 )
232 chunk_size = base_socket.recv_into(buffer, remaining)
233 read_count += chunk_size
234 if not chunk_size:
235 if not read_count:
236 return SecurityConst.errSSLClosedGraceful
237 break
238 except (socket.error) as e:
239 error = e.errno
240
241 if error is not None and error != errno.EAGAIN:
242 data_length_pointer[0] = read_count
243 if error == errno.ECONNRESET or error == errno.EPIPE:
244 return SecurityConst.errSSLClosedAbort
245 raise
246
247 data_length_pointer[0] = read_count
248
249 if read_count != requested_length:
250 return SecurityConst.errSSLWouldBlock
251
252 return 0
253 except Exception as e:
254 if wrapped_socket is not None:
255 wrapped_socket._exception = e
256 return SecurityConst.errSSLInternal
257
258
259 def _write_callback(connection_id, data_buffer, data_length_pointer):
260 """
261 SecureTransport write callback. This is called by ST to request that data
262 actually be sent on the network.
263 """
264 wrapped_socket = None
265 try:
266 wrapped_socket = _connection_refs.get(connection_id)
267 if wrapped_socket is None:
268 return SecurityConst.errSSLInternal
269 base_socket = wrapped_socket.socket
270
271 bytes_to_write = data_length_pointer[0]
272 data = ctypes.string_at(data_buffer, bytes_to_write)
273
274 timeout = wrapped_socket.gettimeout()
275 error = None
276 sent = 0
277
278 try:
279 while sent < bytes_to_write:
280 if timeout is None or timeout >= 0:
281 if not util.wait_for_write(base_socket, timeout):
282 raise socket.error(errno.EAGAIN, "timed out")
283 chunk_sent = base_socket.send(data)
284 sent += chunk_sent
285
286 # This has some needless copying here, but I'm not sure there's
287 # much value in optimising this data path.
288 data = data[chunk_sent:]
289 except (socket.error) as e:
290 error = e.errno
291
292 if error is not None and error != errno.EAGAIN:
293 data_length_pointer[0] = sent
294 if error == errno.ECONNRESET or error == errno.EPIPE:
295 return SecurityConst.errSSLClosedAbort
296 raise
297
298 data_length_pointer[0] = sent
299
300 if sent != bytes_to_write:
301 return SecurityConst.errSSLWouldBlock
302
303 return 0
304 except Exception as e:
305 if wrapped_socket is not None:
306 wrapped_socket._exception = e
307 return SecurityConst.errSSLInternal
308
309
310 # We need to keep these two objects references alive: if they get GC'd while
311 # in use then SecureTransport could attempt to call a function that is in freed
312 # memory. That would be...uh...bad. Yeah, that's the word. Bad.
313 _read_callback_pointer = Security.SSLReadFunc(_read_callback)
314 _write_callback_pointer = Security.SSLWriteFunc(_write_callback)
315
316
317 class WrappedSocket(object):
318 """
319 API-compatibility wrapper for Python's OpenSSL wrapped socket object.
320
321 Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage
322 collector of PyPy.
323 """
324
325 def __init__(self, socket):
326 self.socket = socket
327 self.context = None
328 self._makefile_refs = 0
329 self._closed = False
330 self._exception = None
331 self._keychain = None
332 self._keychain_dir = None
333 self._client_cert_chain = None
334
335 # We save off the previously-configured timeout and then set it to
336 # zero. This is done because we use select and friends to handle the
337 # timeouts, but if we leave the timeout set on the lower socket then
338 # Python will "kindly" call select on that socket again for us. Avoid
339 # that by forcing the timeout to zero.
340 self._timeout = self.socket.gettimeout()
341 self.socket.settimeout(0)
342
343 @contextlib.contextmanager
344 def _raise_on_error(self):
345 """
346 A context manager that can be used to wrap calls that do I/O from
347 SecureTransport. If any of the I/O callbacks hit an exception, this
348 context manager will correctly propagate the exception after the fact.
349 This avoids silently swallowing those exceptions.
350
351 It also correctly forces the socket closed.
352 """
353 self._exception = None
354
355 # We explicitly don't catch around this yield because in the unlikely
356 # event that an exception was hit in the block we don't want to swallow
357 # it.
358 yield
359 if self._exception is not None:
360 exception, self._exception = self._exception, None
361 self.close()
362 raise exception
363
364 def _set_ciphers(self):
365 """
366 Sets up the allowed ciphers. By default this matches the set in
367 util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done
368 custom and doesn't allow changing at this time, mostly because parsing
369 OpenSSL cipher strings is going to be a freaking nightmare.
370 """
371 ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES)
372 result = Security.SSLSetEnabledCiphers(
373 self.context, ciphers, len(CIPHER_SUITES)
374 )
375 _assert_no_error(result)
376
377 def _custom_validate(self, verify, trust_bundle):
378 """
379 Called when we have set custom validation. We do this in two cases:
380 first, when cert validation is entirely disabled; and second, when
381 using a custom trust DB.
382 """
383 # If we disabled cert validation, just say: cool.
384 if not verify:
385 return
386
387 # We want data in memory, so load it up.
388 if os.path.isfile(trust_bundle):
389 with open(trust_bundle, "rb") as f:
390 trust_bundle = f.read()
391
392 cert_array = None
393 trust = Security.SecTrustRef()
394
395 try:
396 # Get a CFArray that contains the certs we want.
397 cert_array = _cert_array_from_pem(trust_bundle)
398
399 # Ok, now the hard part. We want to get the SecTrustRef that ST has
400 # created for this connection, shove our CAs into it, tell ST to
401 # ignore everything else it knows, and then ask if it can build a
402 # chain. This is a buuuunch of code.
403 result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust))
404 _assert_no_error(result)
405 if not trust:
406 raise ssl.SSLError("Failed to copy trust reference")
407
408 result = Security.SecTrustSetAnchorCertificates(trust, cert_array)
409 _assert_no_error(result)
410
411 result = Security.SecTrustSetAnchorCertificatesOnly(trust, True)
412 _assert_no_error(result)
413
414 trust_result = Security.SecTrustResultType()
415 result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result))
416 _assert_no_error(result)
417 finally:
418 if trust:
419 CoreFoundation.CFRelease(trust)
420
421 if cert_array is not None:
422 CoreFoundation.CFRelease(cert_array)
423
424 # Ok, now we can look at what the result was.
425 successes = (
426 SecurityConst.kSecTrustResultUnspecified,
427 SecurityConst.kSecTrustResultProceed,
428 )
429 if trust_result.value not in successes:
430 raise ssl.SSLError(
431 "certificate verify failed, error code: %d" % trust_result.value
432 )
433
434 def handshake(
435 self,
436 server_hostname,
437 verify,
438 trust_bundle,
439 min_version,
440 max_version,
441 client_cert,
442 client_key,
443 client_key_passphrase,
444 ):
445 """
446 Actually performs the TLS handshake. This is run automatically by
447 wrapped socket, and shouldn't be needed in user code.
448 """
449 # First, we do the initial bits of connection setup. We need to create
450 # a context, set its I/O funcs, and set the connection reference.
451 self.context = Security.SSLCreateContext(
452 None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType
453 )
454 result = Security.SSLSetIOFuncs(
455 self.context, _read_callback_pointer, _write_callback_pointer
456 )
457 _assert_no_error(result)
458
459 # Here we need to compute the handle to use. We do this by taking the
460 # id of self modulo 2**31 - 1. If this is already in the dictionary, we
461 # just keep incrementing by one until we find a free space.
462 with _connection_ref_lock:
463 handle = id(self) % 2147483647
464 while handle in _connection_refs:
465 handle = (handle + 1) % 2147483647
466 _connection_refs[handle] = self
467
468 result = Security.SSLSetConnection(self.context, handle)
469 _assert_no_error(result)
470
471 # If we have a server hostname, we should set that too.
472 if server_hostname:
473 if not isinstance(server_hostname, bytes):
474 server_hostname = server_hostname.encode("utf-8")
475
476 result = Security.SSLSetPeerDomainName(
477 self.context, server_hostname, len(server_hostname)
478 )
479 _assert_no_error(result)
480
481 # Setup the ciphers.
482 self._set_ciphers()
483
484 # Set the minimum and maximum TLS versions.
485 result = Security.SSLSetProtocolVersionMin(self.context, min_version)
486 _assert_no_error(result)
487
488 result = Security.SSLSetProtocolVersionMax(self.context, max_version)
489 _assert_no_error(result)
490
491 # If there's a trust DB, we need to use it. We do that by telling
492 # SecureTransport to break on server auth. We also do that if we don't
493 # want to validate the certs at all: we just won't actually do any
494 # authing in that case.
495 if not verify or trust_bundle is not None:
496 result = Security.SSLSetSessionOption(
497 self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True
498 )
499 _assert_no_error(result)
500
501 # If there's a client cert, we need to use it.
502 if client_cert:
503 self._keychain, self._keychain_dir = _temporary_keychain()
504 self._client_cert_chain = _load_client_cert_chain(
505 self._keychain, client_cert, client_key
506 )
507 result = Security.SSLSetCertificate(self.context, self._client_cert_chain)
508 _assert_no_error(result)
509
510 while True:
511 with self._raise_on_error():
512 result = Security.SSLHandshake(self.context)
513
514 if result == SecurityConst.errSSLWouldBlock:
515 raise socket.timeout("handshake timed out")
516 elif result == SecurityConst.errSSLServerAuthCompleted:
517 self._custom_validate(verify, trust_bundle)
518 continue
519 else:
520 _assert_no_error(result)
521 break
522
523 def fileno(self):
524 return self.socket.fileno()
525
526 # Copy-pasted from Python 3.5 source code
527 def _decref_socketios(self):
528 if self._makefile_refs > 0:
529 self._makefile_refs -= 1
530 if self._closed:
531 self.close()
532
533 def recv(self, bufsiz):
534 buffer = ctypes.create_string_buffer(bufsiz)
535 bytes_read = self.recv_into(buffer, bufsiz)
536 data = buffer[:bytes_read]
537 return data
538
539 def recv_into(self, buffer, nbytes=None):
540 # Read short on EOF.
541 if self._closed:
542 return 0
543
544 if nbytes is None:
545 nbytes = len(buffer)
546
547 buffer = (ctypes.c_char * nbytes).from_buffer(buffer)
548 processed_bytes = ctypes.c_size_t(0)
549
550 with self._raise_on_error():
551 result = Security.SSLRead(
552 self.context, buffer, nbytes, ctypes.byref(processed_bytes)
553 )
554
555 # There are some result codes that we want to treat as "not always
556 # errors". Specifically, those are errSSLWouldBlock,
557 # errSSLClosedGraceful, and errSSLClosedNoNotify.
558 if result == SecurityConst.errSSLWouldBlock:
559 # If we didn't process any bytes, then this was just a time out.
560 # However, we can get errSSLWouldBlock in situations when we *did*
561 # read some data, and in those cases we should just read "short"
562 # and return.
563 if processed_bytes.value == 0:
564 # Timed out, no data read.
565 raise socket.timeout("recv timed out")
566 elif result in (
567 SecurityConst.errSSLClosedGraceful,
568 SecurityConst.errSSLClosedNoNotify,
569 ):
570 # The remote peer has closed this connection. We should do so as
571 # well. Note that we don't actually return here because in
572 # principle this could actually be fired along with return data.
573 # It's unlikely though.
574 self.close()
575 else:
576 _assert_no_error(result)
577
578 # Ok, we read and probably succeeded. We should return whatever data
579 # was actually read.
580 return processed_bytes.value
581
582 def settimeout(self, timeout):
583 self._timeout = timeout
584
585 def gettimeout(self):
586 return self._timeout
587
588 def send(self, data):
589 processed_bytes = ctypes.c_size_t(0)
590
591 with self._raise_on_error():
592 result = Security.SSLWrite(
593 self.context, data, len(data), ctypes.byref(processed_bytes)
594 )
595
596 if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0:
597 # Timed out
598 raise socket.timeout("send timed out")
599 else:
600 _assert_no_error(result)
601
602 # We sent, and probably succeeded. Tell them how much we sent.
603 return processed_bytes.value
604
605 def sendall(self, data):
606 total_sent = 0
607 while total_sent < len(data):
608 sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE])
609 total_sent += sent
610
611 def shutdown(self):
612 with self._raise_on_error():
613 Security.SSLClose(self.context)
614
615 def close(self):
616 # TODO: should I do clean shutdown here? Do I have to?
617 if self._makefile_refs < 1:
618 self._closed = True
619 if self.context:
620 CoreFoundation.CFRelease(self.context)
621 self.context = None
622 if self._client_cert_chain:
623 CoreFoundation.CFRelease(self._client_cert_chain)
624 self._client_cert_chain = None
625 if self._keychain:
626 Security.SecKeychainDelete(self._keychain)
627 CoreFoundation.CFRelease(self._keychain)
628 shutil.rmtree(self._keychain_dir)
629 self._keychain = self._keychain_dir = None
630 return self.socket.close()
631 else:
632 self._makefile_refs -= 1
633
634 def getpeercert(self, binary_form=False):
635 # Urgh, annoying.
636 #
637 # Here's how we do this:
638 #
639 # 1. Call SSLCopyPeerTrust to get hold of the trust object for this
640 # connection.
641 # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf.
642 # 3. To get the CN, call SecCertificateCopyCommonName and process that
643 # string so that it's of the appropriate type.
644 # 4. To get the SAN, we need to do something a bit more complex:
645 # a. Call SecCertificateCopyValues to get the data, requesting
646 # kSecOIDSubjectAltName.
647 # b. Mess about with this dictionary to try to get the SANs out.
648 #
649 # This is gross. Really gross. It's going to be a few hundred LoC extra
650 # just to repeat something that SecureTransport can *already do*. So my
651 # operating assumption at this time is that what we want to do is
652 # instead to just flag to urllib3 that it shouldn't do its own hostname
653 # validation when using SecureTransport.
654 if not binary_form:
655 raise ValueError("SecureTransport only supports dumping binary certs")
656 trust = Security.SecTrustRef()
657 certdata = None
658 der_bytes = None
659
660 try:
661 # Grab the trust store.
662 result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust))
663 _assert_no_error(result)
664 if not trust:
665 # Probably we haven't done the handshake yet. No biggie.
666 return None
667
668 cert_count = Security.SecTrustGetCertificateCount(trust)
669 if not cert_count:
670 # Also a case that might happen if we haven't handshaked.
671 # Handshook? Handshaken?
672 return None
673
674 leaf = Security.SecTrustGetCertificateAtIndex(trust, 0)
675 assert leaf
676
677 # Ok, now we want the DER bytes.
678 certdata = Security.SecCertificateCopyData(leaf)
679 assert certdata
680
681 data_length = CoreFoundation.CFDataGetLength(certdata)
682 data_buffer = CoreFoundation.CFDataGetBytePtr(certdata)
683 der_bytes = ctypes.string_at(data_buffer, data_length)
684 finally:
685 if certdata:
686 CoreFoundation.CFRelease(certdata)
687 if trust:
688 CoreFoundation.CFRelease(trust)
689
690 return der_bytes
691
692 def version(self):
693 protocol = Security.SSLProtocol()
694 result = Security.SSLGetNegotiatedProtocolVersion(
695 self.context, ctypes.byref(protocol)
696 )
697 _assert_no_error(result)
698 if protocol.value == SecurityConst.kTLSProtocol13:
699 raise ssl.SSLError("SecureTransport does not support TLS 1.3")
700 elif protocol.value == SecurityConst.kTLSProtocol12:
701 return "TLSv1.2"
702 elif protocol.value == SecurityConst.kTLSProtocol11:
703 return "TLSv1.1"
704 elif protocol.value == SecurityConst.kTLSProtocol1:
705 return "TLSv1"
706 elif protocol.value == SecurityConst.kSSLProtocol3:
707 return "SSLv3"
708 elif protocol.value == SecurityConst.kSSLProtocol2:
709 return "SSLv2"
710 else:
711 raise ssl.SSLError("Unknown TLS version: %r" % protocol)
712
713 def _reuse(self):
714 self._makefile_refs += 1
715
716 def _drop(self):
717 if self._makefile_refs < 1:
718 self.close()
719 else:
720 self._makefile_refs -= 1
721
722
723 if _fileobject: # Platform-specific: Python 2
724
725 def makefile(self, mode, bufsize=-1):
726 self._makefile_refs += 1
727 return _fileobject(self, mode, bufsize, close=True)
728
729
730 else: # Platform-specific: Python 3
731
732 def makefile(self, mode="r", buffering=None, *args, **kwargs):
733 # We disable buffering with SecureTransport because it conflicts with
734 # the buffering that ST does internally (see issue #1153 for more).
735 buffering = 0
736 return backport_makefile(self, mode, buffering, *args, **kwargs)
737
738
739 WrappedSocket.makefile = makefile
740
741
742 class SecureTransportContext(object):
743 """
744 I am a wrapper class for the SecureTransport library, to translate the
745 interface of the standard library ``SSLContext`` object to calls into
746 SecureTransport.
747 """
748
749 def __init__(self, protocol):
750 self._min_version, self._max_version = _protocol_to_min_max[protocol]
751 self._options = 0
752 self._verify = False
753 self._trust_bundle = None
754 self._client_cert = None
755 self._client_key = None
756 self._client_key_passphrase = None
757
758 @property
759 def check_hostname(self):
760 """
761 SecureTransport cannot have its hostname checking disabled. For more,
762 see the comment on getpeercert() in this file.
763 """
764 return True
765
766 @check_hostname.setter
767 def check_hostname(self, value):
768 """
769 SecureTransport cannot have its hostname checking disabled. For more,
770 see the comment on getpeercert() in this file.
771 """
772 pass
773
774 @property
775 def options(self):
776 # TODO: Well, crap.
777 #
778 # So this is the bit of the code that is the most likely to cause us
779 # trouble. Essentially we need to enumerate all of the SSL options that
780 # users might want to use and try to see if we can sensibly translate
781 # them, or whether we should just ignore them.
782 return self._options
783
784 @options.setter
785 def options(self, value):
786 # TODO: Update in line with above.
787 self._options = value
788
789 @property
790 def verify_mode(self):
791 return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE
792
793 @verify_mode.setter
794 def verify_mode(self, value):
795 self._verify = True if value == ssl.CERT_REQUIRED else False
796
797 def set_default_verify_paths(self):
798 # So, this has to do something a bit weird. Specifically, what it does
799 # is nothing.
800 #
801 # This means that, if we had previously had load_verify_locations
802 # called, this does not undo that. We need to do that because it turns
803 # out that the rest of the urllib3 code will attempt to load the
804 # default verify paths if it hasn't been told about any paths, even if
805 # the context itself was sometime earlier. We resolve that by just
806 # ignoring it.
807 pass
808
809 def load_default_certs(self):
810 return self.set_default_verify_paths()
811
812 def set_ciphers(self, ciphers):
813 # For now, we just require the default cipher string.
814 if ciphers != util.ssl_.DEFAULT_CIPHERS:
815 raise ValueError("SecureTransport doesn't support custom cipher strings")
816
817 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
818 # OK, we only really support cadata and cafile.
819 if capath is not None:
820 raise ValueError("SecureTransport does not support cert directories")
821
822 # Raise if cafile does not exist.
823 if cafile is not None:
824 with open(cafile):
825 pass
826
827 self._trust_bundle = cafile or cadata
828
829 def load_cert_chain(self, certfile, keyfile=None, password=None):
830 self._client_cert = certfile
831 self._client_key = keyfile
832 self._client_cert_passphrase = password
833
834 def wrap_socket(
835 self,
836 sock,
837 server_side=False,
838 do_handshake_on_connect=True,
839 suppress_ragged_eofs=True,
840 server_hostname=None,
841 ):
842 # So, what do we do here? Firstly, we assert some properties. This is a
843 # stripped down shim, so there is some functionality we don't support.
844 # See PEP 543 for the real deal.
845 assert not server_side
846 assert do_handshake_on_connect
847 assert suppress_ragged_eofs
848
849 # Ok, we're good to go. Now we want to create the wrapped socket object
850 # and store it in the appropriate place.
851 wrapped_socket = WrappedSocket(sock)
852
853 # Now we can handshake
854 wrapped_socket.handshake(
855 server_hostname,
856 self._verify,
857 self._trust_bundle,
858 self._min_version,
859 self._max_version,
860 self._client_cert,
861 self._client_key,
862 self._client_key_passphrase,
863 )
864 return wrapped_socket