Configuring TLS With Requests

This post was originally published here

A common problem encountered by Requests users is that they need to perform some specific configuration of TLS. This can happen for a number of reasons, but the most common problem is that Requests has a default TLS configuration that is fairly strict. In particular, we recently removed support for all cipher suites that use the 3DES stream cipher. Unfortunately, for many older servers (particularly those that do not support TLSv1.1 or TLSv1.2), these were the last cipher suites we had in common with those servers.

Now, removing 3DES was in general the right thing to do. Recent advances in cryptanalysis mean that 3DES is insecure for bulk-transfer: a long-lived connection that transfers a large amount of data using 3DES can be attacked and can have encrypted data exfiltrated by a determined attacker. Of course, for many users this is not a plausible attack vector (for example, one-off scripts that do batch work), but we need to protect all our users, and the only way to ensure that users are not accidentally exposed to this attack is to remove it from our list altogether.

Naturally, a number of users want to add this back. Historically this was a difficult thing to do in Requests, but in more recent versions (since v2.12.0) it has become possible to get extremely low-level configuration of Requests’ TLS settings on a per-host level. This blog post will demonstrate how to do this to specifically re-add 3DES support for a single host, but in general this allows arbitrarily-detailed TLS configuration.

How It Works

The feature added in Requests v2.12.0 is that urllib3 now accepts an SSLContext object in the constructors for ConnectionPool objects. This SSLContext will be used as the factory for the underlying TLS connection, and so all settings applied to it will also be applied to those low-level connections.

The best way to do this is to use the SSLContext factory function requests.packages.urllib3.util.ssl_.create_urllib3_context. This is analogous to Python’s ssl.create_default_context function but applies the more-strict default TLS configuration that Requests and urllib3 both use. This function will return an SSLContext object that can then have further configuration applied. On top of that, the function also takes a few arguments to allow overriding default configuration.

To provide the new SSLContext object, you will need to write a TransportAdapter that is appropriate for the given host.

Below is an example of how to re-enable 3DES in Requests using this method.

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context

# This is the 2.11 Requests cipher string, containing 3DES.
CIPHERS = (
    'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
    'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
    '!eNULL:!MD5'
)


class DESAdapter(HTTPAdapter):
    """
    A TransportAdapter that re-enables 3DES support in Requests.
    """
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=CIPHERS)
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=CIPHERS)
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)

s = requests.Session()
s.mount('https://some-3des-only-host.com', DESAdapter())
r = s.get('https://some-3des-only-host.com/some-path')

This is all you need to do! This works for essentially all TLS configuration you might want to do. Let us know if you have further problems with configuring TLS in Requests.


Related Posts

Python’s Instance, Class, and Static Methods Demystified In this tutorial I’ll help demystify what’s behind class methods, static methods, and regular instance methods. If you develop an intuitiv...
How to do Descriptives Statistics in Python using Numpy In this short post we are going to revisit the topic on how to carry out summary/descriptive statistics in Python. In the previous post, I used Pandas...
A Magical Introduction to Classification Algorithms by Bryan Berend | March 23, 2017 About Bryan: Bryan is the Lead Data Scientist at Nielsen. Introduction When you first start learning about data sci...
Turbocharge Your Data Acquisition using the data.world Python Library When working with data, a key part of your workflow is finding and importing data sets. Being able to quickly locate data, understand it and combine i...

Leave a Reply

Be the First to Comment!

Notify of
avatar
wpDiscuz