Skip to content

Commit

Permalink
Add ALPN support with SecureTransport
Browse files Browse the repository at this point in the history
  • Loading branch information
hodbn committed Jul 14, 2020
1 parent 1f82484 commit a47ea10
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/urllib3/contrib/_securetransport/bindings.py
Expand Up @@ -276,6 +276,13 @@
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

Expand Down
43 changes: 43 additions & 0 deletions src/urllib3/contrib/_securetransport/low_level.py
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions src/urllib3/contrib/securetransport.py
Expand Up @@ -56,6 +56,7 @@
import errno
import os.path
import shutil
import six
import socket
import ssl
import threading
Expand All @@ -68,6 +69,7 @@
_cert_array_from_pem,
_temporary_keychain,
_load_client_cert_chain,
_create_cfstring_array,
)

try: # Platform-specific: Python 2
Expand Down Expand Up @@ -374,6 +376,19 @@ def _set_ciphers(self):
)
_assert_no_error(result)

def _set_alpn_protocols(self, protocols):
"""
Sets up the ALPN protocols on the context.
"""
if not protocols:
return
protos_arr = _create_cfstring_array(protocols)
try:
result = Security.SSLSetALPNProtocols(self.context, protos_arr)
_assert_no_error(result)
finally:
CoreFoundation.CFRelease(protos_arr)

def _custom_validate(self, verify, trust_bundle):
"""
Called when we have set custom validation. We do this in two cases:
Expand Down Expand Up @@ -441,6 +456,7 @@ def handshake(
client_cert,
client_key,
client_key_passphrase,
alpn_protos,
):
"""
Actually performs the TLS handshake. This is run automatically by
Expand Down Expand Up @@ -481,6 +497,9 @@ def handshake(
# Setup the ciphers.
self._set_ciphers()

# Setup the ALPN protocols.
self._set_alpn_protocols(alpn_protos)

# Set the minimum and maximum TLS versions.
result = Security.SSLSetProtocolVersionMin(self.context, min_version)
_assert_no_error(result)
Expand Down Expand Up @@ -754,6 +773,7 @@ def __init__(self, protocol):
self._client_cert = None
self._client_key = None
self._client_key_passphrase = None
self._alpn_protos = None

@property
def check_hostname(self):
Expand Down Expand Up @@ -831,6 +851,18 @@ def load_cert_chain(self, certfile, keyfile=None, password=None):
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_protos = [six.ensure_binary(p) for p in protocols]

def wrap_socket(
self,
sock,
Expand Down Expand Up @@ -860,5 +892,6 @@ def wrap_socket(
self._client_cert,
self._client_key,
self._client_key_passphrase,
self._alpn_protos,
)
return wrapped_socket

0 comments on commit a47ea10

Please sign in to comment.