Skip to main content

SSL and TLS Updates for Python 3.7

SSL and TLS Updates for Python 3.7

Python just received a minor version update to Python 3.7 with many new performance enhancements, added features, and module improvements to the language.   One of the existing Python modules in 3.7 that received some nice new enhancements is the ssl module. The updated ssl module now has enhanced hostname support, updates to how blacklisting and whitelisting work, but most importantly, conditional support for TLS 1.3 connections.   TLS 1.3 connection support is now available to test in your Python application when working with OpenSSL 1.1.1. And that is why I wanted to write this article, to provide a brief summary of how to get started using TLS 1.3 connections and to provide a code sample taken from CPython to illustrate these updates.  Let’s jump in!

NOTE: These examples have been tested on macOS 10.13 and Ubuntu 16.04 with the release branch of Python 3.7 downloaded and compiled locally.  These code samples also require OpenSSL 1.1.1 to be installed on the same machine that Python 3.7 is running on. These examples have not been tested on Windows.

 

OpenSSL 1.1.1

As briefly mentioned above, the code sample used in this article was tested with the OpenSSL pre beta released on June 20th, 2018.  Downloading and installing this version of OpenSSL is relatively straight forward. Navigate to the download page here and download the copy that makes sense for your machine.  Next, navigate to the downloaded directory on your machine and run the make commands to install. Lastly, run the, “openssl version” command to make sure that the new version is being used by your machine.  If you run into files that are still pointing towards your old version you may need to add the path to your newly installed version to your shared libraries path in Linux.

# Make sure your machine is using the correct version of OpenSSL
$ openssl version
OpenSSL 1.1.1-pre8 (beta) 20 Jun 2018
 
# On Linux you may have to add the new path to your shared libraries path
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/path/to/openssl-1.1.1-pre8

 

TLS 1.3 Socket Connections

Linux TLS 1.3

The big news for the ssl module in Python 3.7 is that conditional support for TLS 1.3 connections will be provided while OpenSSL 1.1.1 and TLS 1.3 are being finalized.  This means that you can test out your Python TLS 1.3 connections before the protocol is finalized and full support is baked in OpenSSL.

So how do I get started with TLS 1.3 in Python 3.7 today? The first thing you will need is agree on a supported cipher suite.  The ssl module now supports all AES-GCM and ChaCha20 cipher suites are enabled by default. In the code sample below the client and the server are using the same SSL context that uses the TLS_AES_256_GCM_SHA384 cipher suite. This is a perfect example of a newly supported cipher suite in OpenSSL 1.1.1, Python 3.7, and TLS 1.3.

Next, how do you tell the ssl context to only use TLS 1.3 connections?   Currently, at the time of writing this article, OpenSSL has no dedicated API to set the TLS version.  What you can do is tell the server what not to accept, and that is what the context options are doing in the code sample that I have below. The context options are setting OP_NO for TLS 1.0, TLS 1.1, and TLS 1.2. This forces TLS 1.3 to be used for the connection if available.  

# Small Sample of using new features for ssl in Python 3.7
# ThreadedEchoServer is a code sample used from test_ssl.py in CPython.
 
# Check if OpenSSL has built-in support for the TLS 1.3 protocol.
if ssl.HAS_TLSv1_3:
    print("{0} with support for TLS 1.3"
		  .format(ssl.OPENSSL_VERSION))
 
    # This example is based off of the unit test for bpo-32947
    # written by 
    # https://github.com/python/cpython/pull/5663/files
    CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")
    context = ssl.SSLContext(ssl.PROTOCOL_TLS)
    context.load_cert_chain(CERTFILE)
    context.options |= (
        ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
    )
 
    with ThreadedEchoServer(context=context) as server:
        with context.wrap_socket(socket.socket()) as s:
            s.connect(('localhost', server.port))
            string = "TLS 1.3 Data"
            # Write data to the socket encoded as bytes 
            s.sendall(str.encode(string))
            # Block and read up to 512 bytes from the read buffer.
            data = s.recv(512)
            print("Decoded data: {0}".format(data.decode()))
            s.close()
            server.stop()
 
    thread_info = support.threading_setup()
    support.threading_cleanup(*thread_info)

In Summary

In summary I think that Python 3.7 is really providing valuable support for developers who want to test their applications against TLS 1.3 while OpenSSL and the IETF finalize their changes to the specification.  It may not be end to end support but I think it is a great start in making all Python application faster and more secure. Please let me know what you thought about the article. The code sample from this article is available on my Github here.  If you have any questions, comments, or concerns, please leave a comment.

Member for

3 years 9 months
Matt Eaton

Long time mobile team lead with a love for network engineering, security, IoT, oss, writing, wireless, and mobile.  Avid runner and determined health nut living in the greater Chicagoland area.

Comments

Michael Will

Mon, 12/02/2019 - 06:23 PM

Part of why I like ubuntu and similar distributions is that they are pretty good at tracking security hot fixes and running apt-get update && apt-get upgrade on a regular basis helps.

How do security updates of openssl and other OS components make it into the locally built venv ?

Do we need to rebuild it manually each time or are those dynamically loaded with dlopen at run time?

I ran ldd against a locally built 2.7.16 pyenv python and it only listed
linux-vdso.so.1
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2
libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 \
/lib64/ld-linux-x86-64.so.2