Overcoming TLS Frustrations with Python and macOS Sierra

The short version of this is that you probably want to upgrade to High Sierra (macOS 10.13) if you need to do much of anything with the built-in macOS version of Python and any network tasks.

Some would say: “Why don’t you just install your own, more current, copy of Python?”

Two main reasons:

  1. You are using code that is linked directly to the Apple supplied Python framework (this is true for the bBox FileMaker plug-in)
  2. You want to use things like the Objective-C bindings, which are only in the Apple supplied version of Python

For more information on some things I’ve tried, and a possible workaround for pip upgrade issues, read more below.

If you are using the macOS-supplied copy of Python, you may find that previously-working code fails if using SSL connections. This is largely due to older protocols (SSL v2, etc.) being disabled due to security concerns on servers.

The problem is largely caused by Python being linked to an older version of the OpenSSL library on any OS versions older than macOS High Sierra. We can check this like so:

python -c "import ssl;import OpenSSL;print ssl.OPENSSL_VERSION"

For Sierra, we get “OpenSSL 0.9.8zh 14 Jan 2016” as the output. Unfortunately, TLS 1.2 was not added until OpenSSL version 1.0.1.

Some posts I read online seemed to suggest that upgrading Requests, and perhaps some other modules, would help. I then discovered that both easy_install and pip were broken too:

# easy_install pip
Searching for pip
Reading https://pypi.python.org/simple/pip/
Download error on https://pypi.python.org/simple/pip/: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590) -- Some packages may not be found!

To install or fix pip, I found that you can bootstrap yourself into the latest version  by running the following as root:

curl https://bootstrap.pypa.io/2.6/get-pip.py | python
pip install --upgrade pip

Great, so now I can install a current version of the Requests module with pip. But based on some reading, I’d probably want to install using the “secure” option. Using that led to more errors (only showing significant errors below):

# pip install requests[security]
matplotlib 1.3.1 requires nose, which is not installed.
matplotlib 1.3.1 requires tornado, which is not installed.
...
pyopenssl 17.5.0 has requirement six>=1.5.2, but you'll have six 1.4.1 which is incompatible.
Installing collected packages: idna, urllib3, certifi, chardet, pycparser, cffi, enum34, asn1crypto, ipaddress, cryptography, pyOpenSSL, requests
 Running setup.py install for pycparser ... done
 Found existing installation: pyOpenSSL 0.13.1
Cannot uninstall 'pyOpenSSL'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.

This seems to be yet another rabbit hole, not directly related to the OpenSSL version dependencies. OK, so now installing Requests without the secure option. However, now I get this error when I try to connecting to a server requiring TLS 1.1+:

requests.exceptions.SSLError: HTTPSConnectionPool(host='someserver.beezwax.net', port=443): Max retries exceeded with url: /admin/api/v1/user/login (Caused by SSLError(SSLError(1, u'[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590)'),))

Trying another suggested tack, I attempted to work around this by using the HttpAdapter class with the Requests module but I found that the SSL class does not even define the constants for the newer protocols:

>> ssl.PROTOCOL_TLSv1_1
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'module' object has no attribute 'PROTOCOL_TLSv1_1'

I may test a few further workarounds later, but it’s really looking like network related tasks are a lost cause for this configuration, with the only bright side being that pip can at least be fixed.

  • Simon

Leave a Reply