diff options
| author | hodbn <hodbn@users.noreply.github.com> | 2020-07-16 09:05:57 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-16 11:05:57 -0500 |
| commit | 688584d692f3d77f5d6beca6bb97faef15f0977b (patch) | |
| tree | 5390f148b6e7682ac8eeeeaa549c1b27ff7ee4db /src | |
| parent | fe1f73b69838c04a516170478fd508966854c415 (diff) | |
| download | urllib3-688584d692f3d77f5d6beca6bb97faef15f0977b.tar.gz | |
Send "http/1.1" ALPN extension during TLS handshake
Diffstat (limited to 'src')
| -rw-r--r-- | src/urllib3/contrib/_securetransport/bindings.py | 7 | ||||
| -rw-r--r-- | src/urllib3/contrib/_securetransport/low_level.py | 43 | ||||
| -rw-r--r-- | src/urllib3/contrib/pyopenssl.py | 4 | ||||
| -rw-r--r-- | src/urllib3/contrib/securetransport.py | 33 | ||||
| -rw-r--r-- | src/urllib3/util/__init__.py | 2 | ||||
| -rw-r--r-- | src/urllib3/util/ssl_.py | 7 |
6 files changed, 96 insertions, 0 deletions
diff --git a/src/urllib3/contrib/_securetransport/bindings.py b/src/urllib3/contrib/_securetransport/bindings.py index d9b67333..95c54a62 100644 --- a/src/urllib3/contrib/_securetransport/bindings.py +++ b/src/urllib3/contrib/_securetransport/bindings.py @@ -276,6 +276,13 @@ try: Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMax.restype = OSStatus + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef diff --git a/src/urllib3/contrib/_securetransport/low_level.py b/src/urllib3/contrib/_securetransport/low_level.py index e60168ca..d3222e39 100644 --- a/src/urllib3/contrib/_securetransport/low_level.py +++ b/src/urllib3/contrib/_securetransport/low_level.py @@ -56,6 +56,49 @@ def _cf_dictionary_from_tuples(tuples): ) +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, c_str, CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + def _cf_string_to_unicode(value): """ Creates a Unicode string from a CFString object. Used entirely for error diff --git a/src/urllib3/contrib/pyopenssl.py b/src/urllib3/contrib/pyopenssl.py index 81a80651..43ea9967 100644 --- a/src/urllib3/contrib/pyopenssl.py +++ b/src/urllib3/contrib/pyopenssl.py @@ -465,6 +465,10 @@ class PyOpenSSLContext(object): self._ctx.set_passwd_cb(lambda *_: password) self._ctx.use_privatekey_file(keyfile or certfile) + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) + def wrap_socket( self, sock, diff --git a/src/urllib3/contrib/securetransport.py b/src/urllib3/contrib/securetransport.py index a6b7e94a..f10dfc37 100644 --- a/src/urllib3/contrib/securetransport.py +++ b/src/urllib3/contrib/securetransport.py @@ -56,6 +56,7 @@ import ctypes import errno import os.path import shutil +import six import socket import ssl import threading @@ -68,6 +69,7 @@ from ._securetransport.low_level import ( _cert_array_from_pem, _temporary_keychain, _load_client_cert_chain, + _create_cfstring_array, ) try: # Platform-specific: Python 2 @@ -374,6 +376,19 @@ class WrappedSocket(object): ) _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: @@ -441,6 +456,7 @@ class WrappedSocket(object): client_cert, client_key, client_key_passphrase, + alpn_protocols, ): """ Actually performs the TLS handshake. This is run automatically by @@ -481,6 +497,9 @@ class WrappedSocket(object): # 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) @@ -754,6 +773,7 @@ class SecureTransportContext(object): self._client_cert = None self._client_key = None self._client_key_passphrase = None + self._alpn_protocols = None @property def check_hostname(self): @@ -831,6 +851,18 @@ class SecureTransportContext(object): 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, @@ -860,5 +892,6 @@ class SecureTransportContext(object): self._client_cert, self._client_key, self._client_key_passphrase, + self._alpn_protocols, ) return wrapped_socket diff --git a/src/urllib3/util/__init__.py b/src/urllib3/util/__init__.py index 3fa98c53..24c16a28 100644 --- a/src/urllib3/util/__init__.py +++ b/src/urllib3/util/__init__.py @@ -14,6 +14,7 @@ from .ssl_ import ( resolve_ssl_version, ssl_wrap_socket, PROTOCOL_TLS, + ALPN_PROTOCOLS, ) from .timeout import current_time, Timeout @@ -27,6 +28,7 @@ __all__ = ( "IS_SECURETRANSPORT", "SSLContext", "PROTOCOL_TLS", + "ALPN_PROTOCOLS", "Retry", "Timeout", "Url", diff --git a/src/urllib3/util/ssl_.py b/src/urllib3/util/ssl_.py index 3d89a56c..9a8ccdaa 100644 --- a/src/urllib3/util/ssl_.py +++ b/src/urllib3/util/ssl_.py @@ -17,6 +17,7 @@ SSLContext = None HAS_SNI = False IS_PYOPENSSL = False IS_SECURETRANSPORT = False +ALPN_PROTOCOLS = ["http/1.1"] # Maps the length of a digest to a possible hash function producing this digest HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} @@ -373,6 +374,12 @@ def ssl_wrap_socket( else: context.load_cert_chain(certfile, keyfile, key_password) + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: + pass + # If we detect server_hostname is an IP address then the SNI # extension should not be used according to RFC3546 Section 3.1 # We shouldn't warn the user if SNI isn't available but we would |
