Skip to content

Commit

Permalink
Use system TLS ciphers in OpenSSL 1.1.1+
Browse files Browse the repository at this point in the history
  • Loading branch information
sethmlarson committed Nov 25, 2020
1 parent edbdc34 commit 4323bd3
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 161 deletions.
8 changes: 8 additions & 0 deletions src/urllib3/contrib/pyopenssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ class UnsupportedExtension(Exception):
# SNI always works.
HAS_SNI = True

# Use system TLS ciphers on OpenSSL 1.1.1+
USE_SYSTEM_SSL_CIPHERS = util.ssl_._is_ge_openssl_v1_1_1(
openssl_backend.openssl_version_text(), openssl_backend.openssl_version_number()
)

# Map from urllib3 to PyOpenSSL compatible parameter-values.
_openssl_versions = {
util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD,
Expand Down Expand Up @@ -109,6 +114,7 @@ class UnsupportedExtension(Exception):

orig_util_HAS_SNI = util.HAS_SNI
orig_util_SSLContext = util.ssl_.SSLContext
orig_util_USE_SYSTEM_SSL_CIPHERS = util.ssl_.USE_SYSTEM_SSL_CIPHERS


log = logging.getLogger(__name__)
Expand All @@ -125,6 +131,7 @@ def inject_into_urllib3():
util.ssl_.HAS_SNI = HAS_SNI
util.IS_PYOPENSSL = True
util.ssl_.IS_PYOPENSSL = True
util.ssl_.USE_SYSTEM_SSL_CIPHERS = USE_SYSTEM_SSL_CIPHERS


def extract_from_urllib3():
Expand All @@ -136,6 +143,7 @@ def extract_from_urllib3():
util.ssl_.HAS_SNI = orig_util_HAS_SNI
util.IS_PYOPENSSL = False
util.ssl_.IS_PYOPENSSL = False
util.ssl_.USE_SYSTEM_SSL_CIPHERS = orig_util_USE_SYSTEM_SSL_CIPHERS


def _validate_dependencies_met():
Expand Down
3 changes: 3 additions & 0 deletions src/urllib3/contrib/securetransport.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@

orig_util_HAS_SNI = util.HAS_SNI
orig_util_SSLContext = util.ssl_.SSLContext
orig_util_USE_SYSTEM_SSL_CIPHERS = util.ssl_.USE_SYSTEM_SSL_CIPHERS

# This dictionary is used by the read callback to obtain a handle to the
# calling wrapped socket. This is a pretty silly approach, but for now it'll
Expand Down Expand Up @@ -185,6 +186,7 @@ def inject_into_urllib3():
util.ssl_.HAS_SNI = HAS_SNI
util.IS_SECURETRANSPORT = True
util.ssl_.IS_SECURETRANSPORT = True
util.ssl_.USE_SYSTEM_SSL_CIPHERS = False


def extract_from_urllib3():
Expand All @@ -197,6 +199,7 @@ def extract_from_urllib3():
util.ssl_.HAS_SNI = orig_util_HAS_SNI
util.IS_SECURETRANSPORT = False
util.ssl_.IS_SECURETRANSPORT = False
util.ssl_.USE_SYSTEM_SSL_CIPHERS = orig_util_USE_SYSTEM_SSL_CIPHERS


def _read_callback(connection_id, data_buffer, data_length_pointer):
Expand Down
29 changes: 27 additions & 2 deletions src/urllib3/util/ssl_.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
IS_PYOPENSSL = False
IS_SECURETRANSPORT = False
ALPN_PROTOCOLS = ["http/1.1"]
USE_SYSTEM_SSL_CIPHERS = False

# Maps the length of a digest to a possible hash function producing this digest
HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256}
Expand All @@ -34,19 +35,39 @@ def _const_compare_digest_backport(a, b):

_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport)


def _is_ge_openssl_v1_1_1(
openssl_version_text: str, openssl_version_number: int
) -> bool:
"""Returns True for OpenSSL 1.1.1+ (>=0x10101000)
LibreSSL reports a version number of 0x20000000 for
OpenSSL version number so we need to filter out LibreSSL.
"""
return (
not openssl_version_text.startswith("LibreSSL")
and openssl_version_number >= 0x10101000
)


try: # Do we have ssl at all?
import ssl
from ssl import (
CERT_REQUIRED,
HAS_SNI,
OP_NO_COMPRESSION,
OP_NO_TICKET,
OPENSSL_VERSION,
OPENSSL_VERSION_NUMBER,
PROTOCOL_TLS,
OP_NO_SSLv2,
OP_NO_SSLv3,
SSLContext,
)

USE_SYSTEM_SSL_CIPHERS = _is_ge_openssl_v1_1_1(
OPENSSL_VERSION, OPENSSL_VERSION_NUMBER
)
PROTOCOL_SSLv23 = PROTOCOL_TLS
from .ssltransport import SSLTransport
except ImportError:
Expand Down Expand Up @@ -189,14 +210,18 @@ def create_urllib3_context(
Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``.
:param ciphers:
Which cipher suites to allow the server to select.
Which cipher suites to allow the server to select. Defaults to either system configured
ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers.
:returns:
Constructed SSLContext object with specified options
:rtype: SSLContext
"""
context = SSLContext(ssl_version or PROTOCOL_TLS)

context.set_ciphers(ciphers or DEFAULT_CIPHERS)
# Unless we're given ciphers defer to either system ciphers in
# the case of OpenSSL 1.1.1+ or use our own secure default ciphers.
if ciphers is not None or not USE_SYSTEM_SSL_CIPHERS:
context.set_ciphers(ciphers or DEFAULT_CIPHERS)

# Setting the default here, as we may have no ssl module on import
cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
Expand Down
5 changes: 4 additions & 1 deletion test/contrib/test_pyopenssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def teardown_module():
pass


from ..test_ssl import TestSSL # noqa: E402, F401
from ..test_util import TestUtilSSL # noqa: E402, F401
from ..with_dummyserver.test_https import ( # noqa: E402, F401
TestHTTPS,
Expand All @@ -46,7 +47,9 @@ def teardown_module():
TestClientCerts,
TestSNI,
TestSocketClosing,
TestSSL,
)
from ..with_dummyserver.test_socketlevel import ( # noqa: E402, F401
TestSSL as TestSocketSSL,
)


Expand Down

0 comments on commit 4323bd3

Please sign in to comment.