summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhodbn <hodbn@users.noreply.github.com>2020-07-16 09:05:57 -0700
committerGitHub <noreply@github.com>2020-07-16 11:05:57 -0500
commit688584d692f3d77f5d6beca6bb97faef15f0977b (patch)
tree5390f148b6e7682ac8eeeeaa549c1b27ff7ee4db /src
parentfe1f73b69838c04a516170478fd508966854c415 (diff)
downloadurllib3-688584d692f3d77f5d6beca6bb97faef15f0977b.tar.gz
Send "http/1.1" ALPN extension during TLS handshake
Diffstat (limited to 'src')
-rw-r--r--src/urllib3/contrib/_securetransport/bindings.py7
-rw-r--r--src/urllib3/contrib/_securetransport/low_level.py43
-rw-r--r--src/urllib3/contrib/pyopenssl.py4
-rw-r--r--src/urllib3/contrib/securetransport.py33
-rw-r--r--src/urllib3/util/__init__.py2
-rw-r--r--src/urllib3/util/ssl_.py7
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