For your Friday viewing pleasure I present the following cartoon.

Credit: https://xkcd.com/356/

Background

In case you do not know Apple deprecated the usage of OpenSSL in favor of Common Crypto, back with the release of OS X Lion (10.7) in 2011. On Apple’s latest operating system macOS Sierra (10.12) OpenSSL is currently at version “0.9.8zh” with very little indication that it will get updated. Now this is a specific build that Apple created and it does have a few back-ported fixes however this version doesn’t support TLSv1.1 or TLSv1.2.

If you do not keep up with the latest and greatest in the security world do not worry you are not alone. That is a full time job after all so it is hard for me to keep up with it myself. TLSv1.1 and TLSv1.2 are important because they offer better security. They specifically protect you against two nasty vulnerabilities, BEAST and POODLE if you want more reading.

Fun stuff

So frogor recently worked on a way to patch the ssl module that comes with the system python for macOS. You can see his proof of concept in the tlsssl git repo.

The process for using it looks a little like (partly pseudo code):

  • install homebrew
  • brew install openssl
  • git clone his repo
  • download source files from the github/cpython repo
  • python setup.py build to compile the project
  • Copy the two dylib files to the correct directory
  • Copy the _tlsssl.so and tlsssl.py files to a path that python can import

Followed with the following code to use it:

import tlsssl as ssl
import urllib2
ctx = ssl.create_default_context()
a = urllib2.urlopen('https://fancyssl.hboeck.de/', context=ctx)

And it works! Not too bad for a few minutes of work. However, brew is a big red flag. Thankfully the maintainers learned their lessons and finally resolved my main complaint with Homebrew v1.0.0 specifically the following commit. Homebrew is great for developers and single user machines but once you are on a multiuser system another tool needs to be used.

More fun stuff

For the above patch to work we need an updated version of OpenSSL that supports the updated protocols and we need the patched ssl module. We also would ideally like those files to be placed on the disk and what better than a native package to do so. Before we can get to that step, currently the OpenSSL Software Foundation is maintaining two branches of their code the 1.1.0 series and the 1.0.2 series as Long Term Support (LTS) version1. Since even the latest releases of the python project are still using the 1.0.2 series, as of 6 days ago, along with many other projects I decided we would stick with that series as well.

The MacOps team at Google has been vendoring their own versions of Python and OpenSSL for years, they even open sourced some of their build scripts. My only issue was the lack of customization. Too many variables being hard coded made it difficult to customize for my needs. So I set out to redo all of the build scripts with a new project vendored.

Although the project isn’t complete it has lots of functionality finished. It mainly allows you to create a single package for all the bits frogor did with some bonus built in.

Even more fun stuff

This is great now I can sit and watch my computer build all these projects for 5-10 minutes.. Or you could skip that and go straight to the releases page. Here you’ll find pre-built releases that I’ve created. These come with a few limitations, mainly you don’t get to pick the install paths, package identifiers, or signing cert.

Before we pick apart the output package let’s look at the stock python settings. Run the following on your machine if you want to follow along:

git clone https://github.com/clburlison/vendored.git
cd vendored/tests
/usr/bin/python version_tester.py

The output will be:

Our python is located: /usr/bin/python
Our python version: 2.7.10
Our openssl is: OpenSSL 0.9.8zh 14 Jan 2016
------------------------------------------------------------------
SUCCESS: Connection was made using TLS 1.0

Note the version of OpenSSL and TLS version being used.

Now lets go ahead and download the latest vendored_tlsssl.pkg file from the latest releases page. When you inspect it with Suspicious Package you note that it drops a few files on disk specifically in the /Library/vendored directory and a single file in /Library/Python/2.7/site-packages directory named 000vendored.pth.

If you deem the package safe go ahead and install it on your machine. Now lets run the same script again:

/usr/bin/python version_tester.py

with the output of:

Our python is located: /usr/bin/python
Our python version: 2.7.10
Our openssl is: OpenSSL 1.0.2k  26 Jan 2017
------------------------------------------------------------------
SUCCESS: Connection was made using TLS 1.2

We have a winner! The best part is because how we are adding the patched ssl module to our system python path it becomes very high priority in the list. Verify for yourself:

/usr/bin/python -m site

with the output of:

sys.path = [
    '/Users/vagrant',
    '/Library/vendored/tlsssl',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload',
    '/Library/Python/2.7/site-packages',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python',
    '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC',
]

Notice that /Library/vendored/tlsssl comes before all of the /System/Library paths?

That means using this patch is completely seamless. Just install the single 5.15 MB package and use python2 just like normal.

import urllib2
a = urllib2.urlopen('https://fancyssl.hboeck.de/')

and you are off to the races.

More fun to come

At this time, vendored still has more work. Specifically on creating self contained Python2, Python3 and Ruby. It also needs some love on automating the build of all the tools. Right now build.py is completely unfinished. I also would like to automate the process of building the distribution style packages.

Articles


  1. OpenSSL supported code branches https://www.openssl.org/source/ ↩︎