Mercurial > repos > guerler > springsuite
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 |