Spaces:
Sleeping
Sleeping
whisper-large-v3
/
venv
/lib
/python3.10
/site-packages
/pip
/_vendor
/urllib3
/contrib
/securetransport.py
""" | |
SecureTranport support for urllib3 via ctypes. | |
This makes platform-native TLS available to urllib3 users on macOS without the | |
use of a compiler. This is an important feature because the Python Package | |
Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL | |
that ships with macOS is not capable of doing TLSv1.2. The only way to resolve | |
this is to give macOS users an alternative solution to the problem, and that | |
solution is to use SecureTransport. | |
We use ctypes here because this solution must not require a compiler. That's | |
because pip is not allowed to require a compiler either. | |
This is not intended to be a seriously long-term solution to this problem. | |
The hope is that PEP 543 will eventually solve this issue for us, at which | |
point we can retire this contrib module. But in the short term, we need to | |
solve the impending tire fire that is Python on Mac without this kind of | |
contrib module. So...here we are. | |
To use this module, simply import and inject it:: | |
import pip._vendor.urllib3.contrib.securetransport as securetransport | |
securetransport.inject_into_urllib3() | |
Happy TLSing! | |
This code is a bastardised version of the code found in Will Bond's oscrypto | |
library. An enormous debt is owed to him for blazing this trail for us. For | |
that reason, this code should be considered to be covered both by urllib3's | |
license and by oscrypto's: | |
.. code-block:: | |
Copyright (c) 2015-2016 Will Bond <will@wbond.net> | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
DEALINGS IN THE SOFTWARE. | |
""" | |
from __future__ import absolute_import | |
import contextlib | |
import ctypes | |
import errno | |
import os.path | |
import shutil | |
import socket | |
import ssl | |
import struct | |
import threading | |
import weakref | |
from pip._vendor import six | |
from .. import util | |
from ..util.ssl_ import PROTOCOL_TLS_CLIENT | |
from ._securetransport.bindings import CoreFoundation, Security, SecurityConst | |
from ._securetransport.low_level import ( | |
_assert_no_error, | |
_build_tls_unknown_ca_alert, | |
_cert_array_from_pem, | |
_create_cfstring_array, | |
_load_client_cert_chain, | |
_temporary_keychain, | |
) | |
try: # Platform-specific: Python 2 | |
from socket import _fileobject | |
except ImportError: # Platform-specific: Python 3 | |
_fileobject = None | |
from ..packages.backports.makefile import backport_makefile | |
__all__ = ["inject_into_urllib3", "extract_from_urllib3"] | |
# SNI always works | |
HAS_SNI = True | |
orig_util_HAS_SNI = util.HAS_SNI | |
orig_util_SSLContext = util.ssl_.SSLContext | |
# This dictionary is used by the read callback to obtain a handle to the | |
# calling wrapped socket. This is a pretty silly approach, but for now it'll | |
# do. I feel like I should be able to smuggle a handle to the wrapped socket | |
# directly in the SSLConnectionRef, but for now this approach will work I | |
# guess. | |
# | |
# We need to lock around this structure for inserts, but we don't do it for | |
# reads/writes in the callbacks. The reasoning here goes as follows: | |
# | |
# 1. It is not possible to call into the callbacks before the dictionary is | |
# populated, so once in the callback the id must be in the dictionary. | |
# 2. The callbacks don't mutate the dictionary, they only read from it, and | |
# so cannot conflict with any of the insertions. | |
# | |
# This is good: if we had to lock in the callbacks we'd drastically slow down | |
# the performance of this code. | |
_connection_refs = weakref.WeakValueDictionary() | |
_connection_ref_lock = threading.Lock() | |
# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over | |
# for no better reason than we need *a* limit, and this one is right there. | |
SSL_WRITE_BLOCKSIZE = 16384 | |
# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to | |
# individual cipher suites. We need to do this because this is how | |
# SecureTransport wants them. | |
CIPHER_SUITES = [ | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | |
SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, | |
SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, | |
SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, | |
SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, | |
SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, | |
SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | |
SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, | |
SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, | |
SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, | |
SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | |
SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, | |
SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, | |
SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, | |
SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, | |
SecurityConst.TLS_AES_256_GCM_SHA384, | |
SecurityConst.TLS_AES_128_GCM_SHA256, | |
SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, | |
SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, | |
SecurityConst.TLS_AES_128_CCM_8_SHA256, | |
SecurityConst.TLS_AES_128_CCM_SHA256, | |
SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, | |
SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, | |
SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, | |
SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, | |
] | |
# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of | |
# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. | |
# TLSv1 to 1.2 are supported on macOS 10.8+ | |
_protocol_to_min_max = { | |
util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), | |
PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), | |
} | |
if hasattr(ssl, "PROTOCOL_SSLv2"): | |
_protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( | |
SecurityConst.kSSLProtocol2, | |
SecurityConst.kSSLProtocol2, | |
) | |
if hasattr(ssl, "PROTOCOL_SSLv3"): | |
_protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( | |
SecurityConst.kSSLProtocol3, | |
SecurityConst.kSSLProtocol3, | |
) | |
if hasattr(ssl, "PROTOCOL_TLSv1"): | |
_protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( | |
SecurityConst.kTLSProtocol1, | |
SecurityConst.kTLSProtocol1, | |
) | |
if hasattr(ssl, "PROTOCOL_TLSv1_1"): | |
_protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( | |
SecurityConst.kTLSProtocol11, | |
SecurityConst.kTLSProtocol11, | |
) | |
if hasattr(ssl, "PROTOCOL_TLSv1_2"): | |
_protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( | |
SecurityConst.kTLSProtocol12, | |
SecurityConst.kTLSProtocol12, | |
) | |
def inject_into_urllib3(): | |
""" | |
Monkey-patch urllib3 with SecureTransport-backed SSL-support. | |
""" | |
util.SSLContext = SecureTransportContext | |
util.ssl_.SSLContext = SecureTransportContext | |
util.HAS_SNI = HAS_SNI | |
util.ssl_.HAS_SNI = HAS_SNI | |
util.IS_SECURETRANSPORT = True | |
util.ssl_.IS_SECURETRANSPORT = True | |
def extract_from_urllib3(): | |
""" | |
Undo monkey-patching by :func:`inject_into_urllib3`. | |
""" | |
util.SSLContext = orig_util_SSLContext | |
util.ssl_.SSLContext = orig_util_SSLContext | |
util.HAS_SNI = orig_util_HAS_SNI | |
util.ssl_.HAS_SNI = orig_util_HAS_SNI | |
util.IS_SECURETRANSPORT = False | |
util.ssl_.IS_SECURETRANSPORT = False | |
def _read_callback(connection_id, data_buffer, data_length_pointer): | |
""" | |
SecureTransport read callback. This is called by ST to request that data | |
be returned from the socket. | |
""" | |
wrapped_socket = None | |
try: | |
wrapped_socket = _connection_refs.get(connection_id) | |
if wrapped_socket is None: | |
return SecurityConst.errSSLInternal | |
base_socket = wrapped_socket.socket | |
requested_length = data_length_pointer[0] | |
timeout = wrapped_socket.gettimeout() | |
error = None | |
read_count = 0 | |
try: | |
while read_count < requested_length: | |
if timeout is None or timeout >= 0: | |
if not util.wait_for_read(base_socket, timeout): | |
raise socket.error(errno.EAGAIN, "timed out") | |
remaining = requested_length - read_count | |
buffer = (ctypes.c_char * remaining).from_address( | |
data_buffer + read_count | |
) | |
chunk_size = base_socket.recv_into(buffer, remaining) | |
read_count += chunk_size | |
if not chunk_size: | |
if not read_count: | |
return SecurityConst.errSSLClosedGraceful | |
break | |
except (socket.error) as e: | |
error = e.errno | |
if error is not None and error != errno.EAGAIN: | |
data_length_pointer[0] = read_count | |
if error == errno.ECONNRESET or error == errno.EPIPE: | |
return SecurityConst.errSSLClosedAbort | |
raise | |
data_length_pointer[0] = read_count | |
if read_count != requested_length: | |
return SecurityConst.errSSLWouldBlock | |
return 0 | |
except Exception as e: | |
if wrapped_socket is not None: | |
wrapped_socket._exception = e | |
return SecurityConst.errSSLInternal | |
def _write_callback(connection_id, data_buffer, data_length_pointer): | |
""" | |
SecureTransport write callback. This is called by ST to request that data | |
actually be sent on the network. | |
""" | |
wrapped_socket = None | |
try: | |
wrapped_socket = _connection_refs.get(connection_id) | |
if wrapped_socket is None: | |
return SecurityConst.errSSLInternal | |
base_socket = wrapped_socket.socket | |
bytes_to_write = data_length_pointer[0] | |
data = ctypes.string_at(data_buffer, bytes_to_write) | |
timeout = wrapped_socket.gettimeout() | |
error = None | |
sent = 0 | |
try: | |
while sent < bytes_to_write: | |
if timeout is None or timeout >= 0: | |
if not util.wait_for_write(base_socket, timeout): | |
raise socket.error(errno.EAGAIN, "timed out") | |
chunk_sent = base_socket.send(data) | |
sent += chunk_sent | |
# This has some needless copying here, but I'm not sure there's | |
# much value in optimising this data path. | |
data = data[chunk_sent:] | |
except (socket.error) as e: | |
error = e.errno | |
if error is not None and error != errno.EAGAIN: | |
data_length_pointer[0] = sent | |
if error == errno.ECONNRESET or error == errno.EPIPE: | |
return SecurityConst.errSSLClosedAbort | |
raise | |
data_length_pointer[0] = sent | |
if sent != bytes_to_write: | |
return SecurityConst.errSSLWouldBlock | |
return 0 | |
except Exception as e: | |
if wrapped_socket is not None: | |
wrapped_socket._exception = e | |
return SecurityConst.errSSLInternal | |
# We need to keep these two objects references alive: if they get GC'd while | |
# in use then SecureTransport could attempt to call a function that is in freed | |
# memory. That would be...uh...bad. Yeah, that's the word. Bad. | |
_read_callback_pointer = Security.SSLReadFunc(_read_callback) | |
_write_callback_pointer = Security.SSLWriteFunc(_write_callback) | |
class WrappedSocket(object): | |
""" | |
API-compatibility wrapper for Python's OpenSSL wrapped socket object. | |
Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage | |
collector of PyPy. | |
""" | |
def __init__(self, socket): | |
self.socket = socket | |
self.context = None | |
self._makefile_refs = 0 | |
self._closed = False | |
self._exception = None | |
self._keychain = None | |
self._keychain_dir = None | |
self._client_cert_chain = None | |
# We save off the previously-configured timeout and then set it to | |
# zero. This is done because we use select and friends to handle the | |
# timeouts, but if we leave the timeout set on the lower socket then | |
# Python will "kindly" call select on that socket again for us. Avoid | |
# that by forcing the timeout to zero. | |
self._timeout = self.socket.gettimeout() | |
self.socket.settimeout(0) | |
def _raise_on_error(self): | |
""" | |
A context manager that can be used to wrap calls that do I/O from | |
SecureTransport. If any of the I/O callbacks hit an exception, this | |
context manager will correctly propagate the exception after the fact. | |
This avoids silently swallowing those exceptions. | |
It also correctly forces the socket closed. | |
""" | |
self._exception = None | |
# We explicitly don't catch around this yield because in the unlikely | |
# event that an exception was hit in the block we don't want to swallow | |
# it. | |
yield | |
if self._exception is not None: | |
exception, self._exception = self._exception, None | |
self.close() | |
raise exception | |
def _set_ciphers(self): | |
""" | |
Sets up the allowed ciphers. By default this matches the set in | |
util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done | |
custom and doesn't allow changing at this time, mostly because parsing | |
OpenSSL cipher strings is going to be a freaking nightmare. | |
""" | |
ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) | |
result = Security.SSLSetEnabledCiphers( | |
self.context, ciphers, len(CIPHER_SUITES) | |
) | |
_assert_no_error(result) | |
def _set_alpn_protocols(self, protocols): | |
""" | |
Sets up the ALPN protocols on the context. | |
""" | |
if not protocols: | |
return | |
protocols_arr = _create_cfstring_array(protocols) | |
try: | |
result = Security.SSLSetALPNProtocols(self.context, protocols_arr) | |
_assert_no_error(result) | |
finally: | |
CoreFoundation.CFRelease(protocols_arr) | |
def _custom_validate(self, verify, trust_bundle): | |
""" | |
Called when we have set custom validation. We do this in two cases: | |
first, when cert validation is entirely disabled; and second, when | |
using a custom trust DB. | |
Raises an SSLError if the connection is not trusted. | |
""" | |
# If we disabled cert validation, just say: cool. | |
if not verify: | |
return | |
successes = ( | |
SecurityConst.kSecTrustResultUnspecified, | |
SecurityConst.kSecTrustResultProceed, | |
) | |
try: | |
trust_result = self._evaluate_trust(trust_bundle) | |
if trust_result in successes: | |
return | |
reason = "error code: %d" % (trust_result,) | |
except Exception as e: | |
# Do not trust on error | |
reason = "exception: %r" % (e,) | |
# SecureTransport does not send an alert nor shuts down the connection. | |
rec = _build_tls_unknown_ca_alert(self.version()) | |
self.socket.sendall(rec) | |
# close the connection immediately | |
# l_onoff = 1, activate linger | |
# l_linger = 0, linger for 0 seoncds | |
opts = struct.pack("ii", 1, 0) | |
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) | |
self.close() | |
raise ssl.SSLError("certificate verify failed, %s" % reason) | |
def _evaluate_trust(self, trust_bundle): | |
# We want data in memory, so load it up. | |
if os.path.isfile(trust_bundle): | |
with open(trust_bundle, "rb") as f: | |
trust_bundle = f.read() | |
cert_array = None | |
trust = Security.SecTrustRef() | |
try: | |
# Get a CFArray that contains the certs we want. | |
cert_array = _cert_array_from_pem(trust_bundle) | |
# Ok, now the hard part. We want to get the SecTrustRef that ST has | |
# created for this connection, shove our CAs into it, tell ST to | |
# ignore everything else it knows, and then ask if it can build a | |
# chain. This is a buuuunch of code. | |
result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) | |
_assert_no_error(result) | |
if not trust: | |
raise ssl.SSLError("Failed to copy trust reference") | |
result = Security.SecTrustSetAnchorCertificates(trust, cert_array) | |
_assert_no_error(result) | |
result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) | |
_assert_no_error(result) | |
trust_result = Security.SecTrustResultType() | |
result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) | |
_assert_no_error(result) | |
finally: | |
if trust: | |
CoreFoundation.CFRelease(trust) | |
if cert_array is not None: | |
CoreFoundation.CFRelease(cert_array) | |
return trust_result.value | |
def handshake( | |
self, | |
server_hostname, | |
verify, | |
trust_bundle, | |
min_version, | |
max_version, | |
client_cert, | |
client_key, | |
client_key_passphrase, | |
alpn_protocols, | |
): | |
""" | |
Actually performs the TLS handshake. This is run automatically by | |
wrapped socket, and shouldn't be needed in user code. | |
""" | |
# First, we do the initial bits of connection setup. We need to create | |
# a context, set its I/O funcs, and set the connection reference. | |
self.context = Security.SSLCreateContext( | |
None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType | |
) | |
result = Security.SSLSetIOFuncs( | |
self.context, _read_callback_pointer, _write_callback_pointer | |
) | |
_assert_no_error(result) | |
# Here we need to compute the handle to use. We do this by taking the | |
# id of self modulo 2**31 - 1. If this is already in the dictionary, we | |
# just keep incrementing by one until we find a free space. | |
with _connection_ref_lock: | |
handle = id(self) % 2147483647 | |
while handle in _connection_refs: | |
handle = (handle + 1) % 2147483647 | |
_connection_refs[handle] = self | |
result = Security.SSLSetConnection(self.context, handle) | |
_assert_no_error(result) | |
# If we have a server hostname, we should set that too. | |
if server_hostname: | |
if not isinstance(server_hostname, bytes): | |
server_hostname = server_hostname.encode("utf-8") | |
result = Security.SSLSetPeerDomainName( | |
self.context, server_hostname, len(server_hostname) | |
) | |
_assert_no_error(result) | |
# Setup the ciphers. | |
self._set_ciphers() | |
# Setup the ALPN protocols. | |
self._set_alpn_protocols(alpn_protocols) | |
# Set the minimum and maximum TLS versions. | |
result = Security.SSLSetProtocolVersionMin(self.context, min_version) | |
_assert_no_error(result) | |
result = Security.SSLSetProtocolVersionMax(self.context, max_version) | |
_assert_no_error(result) | |
# If there's a trust DB, we need to use it. We do that by telling | |
# SecureTransport to break on server auth. We also do that if we don't | |
# want to validate the certs at all: we just won't actually do any | |
# authing in that case. | |
if not verify or trust_bundle is not None: | |
result = Security.SSLSetSessionOption( | |
self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True | |
) | |
_assert_no_error(result) | |
# If there's a client cert, we need to use it. | |
if client_cert: | |
self._keychain, self._keychain_dir = _temporary_keychain() | |
self._client_cert_chain = _load_client_cert_chain( | |
self._keychain, client_cert, client_key | |
) | |
result = Security.SSLSetCertificate(self.context, self._client_cert_chain) | |
_assert_no_error(result) | |
while True: | |
with self._raise_on_error(): | |
result = Security.SSLHandshake(self.context) | |
if result == SecurityConst.errSSLWouldBlock: | |
raise socket.timeout("handshake timed out") | |
elif result == SecurityConst.errSSLServerAuthCompleted: | |
self._custom_validate(verify, trust_bundle) | |
continue | |
else: | |
_assert_no_error(result) | |
break | |
def fileno(self): | |
return self.socket.fileno() | |
# Copy-pasted from Python 3.5 source code | |
def _decref_socketios(self): | |
if self._makefile_refs > 0: | |
self._makefile_refs -= 1 | |
if self._closed: | |
self.close() | |
def recv(self, bufsiz): | |
buffer = ctypes.create_string_buffer(bufsiz) | |
bytes_read = self.recv_into(buffer, bufsiz) | |
data = buffer[:bytes_read] | |
return data | |
def recv_into(self, buffer, nbytes=None): | |
# Read short on EOF. | |
if self._closed: | |
return 0 | |
if nbytes is None: | |
nbytes = len(buffer) | |
buffer = (ctypes.c_char * nbytes).from_buffer(buffer) | |
processed_bytes = ctypes.c_size_t(0) | |
with self._raise_on_error(): | |
result = Security.SSLRead( | |
self.context, buffer, nbytes, ctypes.byref(processed_bytes) | |
) | |
# There are some result codes that we want to treat as "not always | |
# errors". Specifically, those are errSSLWouldBlock, | |
# errSSLClosedGraceful, and errSSLClosedNoNotify. | |
if result == SecurityConst.errSSLWouldBlock: | |
# If we didn't process any bytes, then this was just a time out. | |
# However, we can get errSSLWouldBlock in situations when we *did* | |
# read some data, and in those cases we should just read "short" | |
# and return. | |
if processed_bytes.value == 0: | |
# Timed out, no data read. | |
raise socket.timeout("recv timed out") | |
elif result in ( | |
SecurityConst.errSSLClosedGraceful, | |
SecurityConst.errSSLClosedNoNotify, | |
): | |
# The remote peer has closed this connection. We should do so as | |
# well. Note that we don't actually return here because in | |
# principle this could actually be fired along with return data. | |
# It's unlikely though. | |
self.close() | |
else: | |
_assert_no_error(result) | |
# Ok, we read and probably succeeded. We should return whatever data | |
# was actually read. | |
return processed_bytes.value | |
def settimeout(self, timeout): | |
self._timeout = timeout | |
def gettimeout(self): | |
return self._timeout | |
def send(self, data): | |
processed_bytes = ctypes.c_size_t(0) | |
with self._raise_on_error(): | |
result = Security.SSLWrite( | |
self.context, data, len(data), ctypes.byref(processed_bytes) | |
) | |
if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: | |
# Timed out | |
raise socket.timeout("send timed out") | |
else: | |
_assert_no_error(result) | |
# We sent, and probably succeeded. Tell them how much we sent. | |
return processed_bytes.value | |
def sendall(self, data): | |
total_sent = 0 | |
while total_sent < len(data): | |
sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) | |
total_sent += sent | |
def shutdown(self): | |
with self._raise_on_error(): | |
Security.SSLClose(self.context) | |
def close(self): | |
# TODO: should I do clean shutdown here? Do I have to? | |
if self._makefile_refs < 1: | |
self._closed = True | |
if self.context: | |
CoreFoundation.CFRelease(self.context) | |
self.context = None | |
if self._client_cert_chain: | |
CoreFoundation.CFRelease(self._client_cert_chain) | |
self._client_cert_chain = None | |
if self._keychain: | |
Security.SecKeychainDelete(self._keychain) | |
CoreFoundation.CFRelease(self._keychain) | |
shutil.rmtree(self._keychain_dir) | |
self._keychain = self._keychain_dir = None | |
return self.socket.close() | |
else: | |
self._makefile_refs -= 1 | |
def getpeercert(self, binary_form=False): | |
# Urgh, annoying. | |
# | |
# Here's how we do this: | |
# | |
# 1. Call SSLCopyPeerTrust to get hold of the trust object for this | |
# connection. | |
# 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. | |
# 3. To get the CN, call SecCertificateCopyCommonName and process that | |
# string so that it's of the appropriate type. | |
# 4. To get the SAN, we need to do something a bit more complex: | |
# a. Call SecCertificateCopyValues to get the data, requesting | |
# kSecOIDSubjectAltName. | |
# b. Mess about with this dictionary to try to get the SANs out. | |
# | |
# This is gross. Really gross. It's going to be a few hundred LoC extra | |
# just to repeat something that SecureTransport can *already do*. So my | |
# operating assumption at this time is that what we want to do is | |
# instead to just flag to urllib3 that it shouldn't do its own hostname | |
# validation when using SecureTransport. | |
if not binary_form: | |
raise ValueError("SecureTransport only supports dumping binary certs") | |
trust = Security.SecTrustRef() | |
certdata = None | |
der_bytes = None | |
try: | |
# Grab the trust store. | |
result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) | |
_assert_no_error(result) | |
if not trust: | |
# Probably we haven't done the handshake yet. No biggie. | |
return None | |
cert_count = Security.SecTrustGetCertificateCount(trust) | |
if not cert_count: | |
# Also a case that might happen if we haven't handshaked. | |
# Handshook? Handshaken? | |
return None | |
leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) | |
assert leaf | |
# Ok, now we want the DER bytes. | |
certdata = Security.SecCertificateCopyData(leaf) | |
assert certdata | |
data_length = CoreFoundation.CFDataGetLength(certdata) | |
data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) | |
der_bytes = ctypes.string_at(data_buffer, data_length) | |
finally: | |
if certdata: | |
CoreFoundation.CFRelease(certdata) | |
if trust: | |
CoreFoundation.CFRelease(trust) | |
return der_bytes | |
def version(self): | |
protocol = Security.SSLProtocol() | |
result = Security.SSLGetNegotiatedProtocolVersion( | |
self.context, ctypes.byref(protocol) | |
) | |
_assert_no_error(result) | |
if protocol.value == SecurityConst.kTLSProtocol13: | |
raise ssl.SSLError("SecureTransport does not support TLS 1.3") | |
elif protocol.value == SecurityConst.kTLSProtocol12: | |
return "TLSv1.2" | |
elif protocol.value == SecurityConst.kTLSProtocol11: | |
return "TLSv1.1" | |
elif protocol.value == SecurityConst.kTLSProtocol1: | |
return "TLSv1" | |
elif protocol.value == SecurityConst.kSSLProtocol3: | |
return "SSLv3" | |
elif protocol.value == SecurityConst.kSSLProtocol2: | |
return "SSLv2" | |
else: | |
raise ssl.SSLError("Unknown TLS version: %r" % protocol) | |
def _reuse(self): | |
self._makefile_refs += 1 | |
def _drop(self): | |
if self._makefile_refs < 1: | |
self.close() | |
else: | |
self._makefile_refs -= 1 | |
if _fileobject: # Platform-specific: Python 2 | |
def makefile(self, mode, bufsize=-1): | |
self._makefile_refs += 1 | |
return _fileobject(self, mode, bufsize, close=True) | |
else: # Platform-specific: Python 3 | |
def makefile(self, mode="r", buffering=None, *args, **kwargs): | |
# We disable buffering with SecureTransport because it conflicts with | |
# the buffering that ST does internally (see issue #1153 for more). | |
buffering = 0 | |
return backport_makefile(self, mode, buffering, *args, **kwargs) | |
WrappedSocket.makefile = makefile | |
class SecureTransportContext(object): | |
""" | |
I am a wrapper class for the SecureTransport library, to translate the | |
interface of the standard library ``SSLContext`` object to calls into | |
SecureTransport. | |
""" | |
def __init__(self, protocol): | |
self._min_version, self._max_version = _protocol_to_min_max[protocol] | |
self._options = 0 | |
self._verify = False | |
self._trust_bundle = None | |
self._client_cert = None | |
self._client_key = None | |
self._client_key_passphrase = None | |
self._alpn_protocols = None | |
def check_hostname(self): | |
""" | |
SecureTransport cannot have its hostname checking disabled. For more, | |
see the comment on getpeercert() in this file. | |
""" | |
return True | |
def check_hostname(self, value): | |
""" | |
SecureTransport cannot have its hostname checking disabled. For more, | |
see the comment on getpeercert() in this file. | |
""" | |
pass | |
def options(self): | |
# TODO: Well, crap. | |
# | |
# So this is the bit of the code that is the most likely to cause us | |
# trouble. Essentially we need to enumerate all of the SSL options that | |
# users might want to use and try to see if we can sensibly translate | |
# them, or whether we should just ignore them. | |
return self._options | |
def options(self, value): | |
# TODO: Update in line with above. | |
self._options = value | |
def verify_mode(self): | |
return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE | |
def verify_mode(self, value): | |
self._verify = True if value == ssl.CERT_REQUIRED else False | |
def set_default_verify_paths(self): | |
# So, this has to do something a bit weird. Specifically, what it does | |
# is nothing. | |
# | |
# This means that, if we had previously had load_verify_locations | |
# called, this does not undo that. We need to do that because it turns | |
# out that the rest of the urllib3 code will attempt to load the | |
# default verify paths if it hasn't been told about any paths, even if | |
# the context itself was sometime earlier. We resolve that by just | |
# ignoring it. | |
pass | |
def load_default_certs(self): | |
return self.set_default_verify_paths() | |
def set_ciphers(self, ciphers): | |
# For now, we just require the default cipher string. | |
if ciphers != util.ssl_.DEFAULT_CIPHERS: | |
raise ValueError("SecureTransport doesn't support custom cipher strings") | |
def load_verify_locations(self, cafile=None, capath=None, cadata=None): | |
# OK, we only really support cadata and cafile. | |
if capath is not None: | |
raise ValueError("SecureTransport does not support cert directories") | |
# Raise if cafile does not exist. | |
if cafile is not None: | |
with open(cafile): | |
pass | |
self._trust_bundle = cafile or cadata | |
def load_cert_chain(self, certfile, keyfile=None, password=None): | |
self._client_cert = certfile | |
self._client_key = keyfile | |
self._client_cert_passphrase = password | |
def set_alpn_protocols(self, protocols): | |
""" | |
Sets the ALPN protocols that will later be set on the context. | |
Raises a NotImplementedError if ALPN is not supported. | |
""" | |
if not hasattr(Security, "SSLSetALPNProtocols"): | |
raise NotImplementedError( | |
"SecureTransport supports ALPN only in macOS 10.12+" | |
) | |
self._alpn_protocols = [six.ensure_binary(p) for p in protocols] | |
def wrap_socket( | |
self, | |
sock, | |
server_side=False, | |
do_handshake_on_connect=True, | |
suppress_ragged_eofs=True, | |
server_hostname=None, | |
): | |
# So, what do we do here? Firstly, we assert some properties. This is a | |
# stripped down shim, so there is some functionality we don't support. | |
# See PEP 543 for the real deal. | |
assert not server_side | |
assert do_handshake_on_connect | |
assert suppress_ragged_eofs | |
# Ok, we're good to go. Now we want to create the wrapped socket object | |
# and store it in the appropriate place. | |
wrapped_socket = WrappedSocket(sock) | |
# Now we can handshake | |
wrapped_socket.handshake( | |
server_hostname, | |
self._verify, | |
self._trust_bundle, | |
self._min_version, | |
self._max_version, | |
self._client_cert, | |
self._client_key, | |
self._client_key_passphrase, | |
self._alpn_protocols, | |
) | |
return wrapped_socket | |