Skip to content

bpo-31453: Add setter for min/max protocol version #5259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,11 @@ Constants

.. versionadded:: 3.2

.. deprecated:: 3.7
The option is deprecated since OpenSSL 1.1.0, use the new
:attr:`SSLContext.minimum_version` and
:attr:`SSLContext.maximum_version` instead.

.. data:: OP_NO_TLSv1_1

Prevents a TLSv1.1 connection. This option is only applicable in conjunction
Expand All @@ -770,6 +775,9 @@ Constants

.. versionadded:: 3.4

.. deprecated:: 3.7
The option is deprecated since OpenSSL 1.1.0.

.. data:: OP_NO_TLSv1_2

Prevents a TLSv1.2 connection. This option is only applicable in conjunction
Expand All @@ -778,6 +786,9 @@ Constants

.. versionadded:: 3.4

.. deprecated:: 3.7
The option is deprecated since OpenSSL 1.1.0.

.. data:: OP_NO_TLSv1_3

Prevents a TLSv1.3 connection. This option is only applicable in conjunction
Expand All @@ -788,6 +799,10 @@ Constants

.. versionadded:: 3.7

.. deprecated:: 3.7
The option is deprecated since OpenSSL 1.1.0. It was added to 2.7.15,
3.6.3 and 3.7.0 for backwards compatibility with OpenSSL 1.0.2.

.. data:: OP_CIPHER_SERVER_PREFERENCE

Use the server's cipher ordering preference, rather than the client's.
Expand Down Expand Up @@ -856,7 +871,7 @@ Constants

.. data:: HAS_ECDH

Whether the OpenSSL library has built-in support for Elliptic Curve-based
Whether the OpenSSL library has built-in support for the Elliptic Curve-based
Diffie-Hellman key exchange. This should be true unless the feature was
explicitly disabled by the distributor.

Expand All @@ -871,14 +886,44 @@ Constants

.. data:: HAS_NPN

Whether the OpenSSL library has built-in support for *Next Protocol
Whether the OpenSSL library has built-in support for the *Next Protocol
Negotiation* as described in the `Application Layer Protocol
Negotiation <https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation>`_.
When true, you can use the :meth:`SSLContext.set_npn_protocols` method to advertise
which protocols you want to support.

.. versionadded:: 3.3

.. data:: HAS_SSLv2

Whether the OpenSSL library has built-in support for the SSL 2.0 protocol.

.. versionadded:: 3.7

.. data:: HAS_SSLv3

Whether the OpenSSL library has built-in support for the SSL 3.0 protocol.

.. versionadded:: 3.7

.. data:: HAS_TLSv1

Whether the OpenSSL library has built-in support for the TLS 1.0 protocol.

.. versionadded:: 3.7

.. data:: HAS_TLSv1_1

Whether the OpenSSL library has built-in support for the TLS 1.1 protocol.

.. versionadded:: 3.7

.. data:: HAS_TLSv1_2

Whether the OpenSSL library has built-in support for the TLS 1.2 protocol.

.. versionadded:: 3.7

.. data:: HAS_TLSv1_3

Whether the OpenSSL library has built-in support for the TLS 1.3 protocol.
Expand Down Expand Up @@ -965,6 +1010,27 @@ Constants

.. versionadded:: 3.6

.. class:: TLSVersion

:class:`enum.IntEnum` collection of SSL and TLS versions for
:attr:`SSLContext.maximum_version` and :attr:`SSLContext.minimum_version`.

.. versionadded:: 3.7

.. attribute:: TLSVersion.MINIMUM_SUPPORTED
.. attribute:: TLSVersion.MAXIMUM_SUPPORTED

The minimum or maximum supported SSL or TLS version. These are magic
constants. Their values don't reflect the lowest and highest available
TLS/SSL versions.

.. attribute:: TLSVersion.SSLv3
.. attribute:: TLSVersion.TLSv1
.. attribute:: TLSVersion.TLSv1_1
.. attribute:: TLSVersion.TLSv1_2
.. attribute:: TLSVersion.TLSv1_3

SSL 3.0 to TLS 1.3.

SSL Sockets
-----------
Expand Down Expand Up @@ -1788,6 +1854,37 @@ to speed up repeated connections from the same clients.

This features requires OpenSSL 0.9.8f or newer.

.. attribute:: SSLContext.maximum_version

A :class:`TLSVersion` enum member representing the highest supported
TLS version. The value defaults to :attr:`TLSVersion.MAXIMUM_SUPPORTED`.
The attribute is read-only for protocols other than :attr:`PROTOCOL_TLS`,
:attr:`PROTOCOL_TLS_CLIENT`, and :attr:`PROTOCOL_TLS_SERVER`.

The attributes :attr:`~SSLContext.maximum_version`,
:attr:`~SSLContext.minimum_version` and
:attr:`SSLContext.options` all affect the supported SSL
and TLS versions of the context. The implementation does not prevent
invalid combination. For example a context with
:attr:`OP_NO_TLSv1_2` in :attr:`~SSLContext.options` and
:attr:`~SSLContext.maximum_version` set to :attr:`TLSVersion.TLSv1_2`
will not be able to establish a TLS 1.2 connection.

.. note::

This attribute is not available unless the ssl module is compiled
with OpenSSL 1.1.0g or newer.

.. attribute:: SSLContext.minimum_version

Like :attr:`SSLContext.maximum_version` except it is the lowest
supported version or :attr:`TLSVersion.MINIMUM_SUPPORTED`.

.. note::

This attribute is not available unless the ssl module is compiled
with OpenSSL 1.1.0g or newer.

.. attribute:: SSLContext.options

An integer representing the set of SSL options enabled on this context.
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ feature. Instances must be created with :class:`~ssl.SSLContext` methods
:meth:`~ssl.SSLContext.wrap_socket` and :meth:`~ssl.SSLContext.wrap_bio`.
(Contributed by Christian Heimes in :issue:`32951`)

OpenSSL 1.1 APIs for setting the minimum and maximum TLS protocol version are
available as as :attr:`~ssl.SSLContext.minimum_version` and
:attr:`~ssl.SSLContext.maximum_version`. Supported protocols are indicated
by new flags like :data:`~ssl.HAS_TLSv1_1`.
(Contributed by Christian Heimes in :issue:`32609`.)

string
------
Expand Down
37 changes: 34 additions & 3 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@
pass


from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_TLSv1_3
from _ssl import _DEFAULT_CIPHERS
from _ssl import _OPENSSL_API_VERSION
from _ssl import (
HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_SSLv2, HAS_SSLv3, HAS_TLSv1,
HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3
)
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION


_IntEnum._convert(
Expand Down Expand Up @@ -153,6 +155,16 @@
_SSLv2_IF_EXISTS = getattr(_SSLMethod, 'PROTOCOL_SSLv2', None)


class TLSVersion(_IntEnum):
MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED
SSLv3 = _ssl.PROTO_SSLv3
TLSv1 = _ssl.PROTO_TLSv1
TLSv1_1 = _ssl.PROTO_TLSv1_1
TLSv1_2 = _ssl.PROTO_TLSv1_2
TLSv1_3 = _ssl.PROTO_TLSv1_3
MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED


if sys.platform == "win32":
from _ssl import enum_certificates, enum_crls

Expand Down Expand Up @@ -467,6 +479,25 @@ def load_default_certs(self, purpose=Purpose.SERVER_AUTH):
self._load_windows_store_certs(storename, purpose)
self.set_default_verify_paths()

if hasattr(_SSLContext, 'minimum_version'):
@property
def minimum_version(self):
return TLSVersion(super().minimum_version)

@minimum_version.setter
def minimum_version(self, value):
if value == TLSVersion.SSLv3:
self.options &= ~Options.OP_NO_SSLv3
super(SSLContext, SSLContext).minimum_version.__set__(self, value)

@property
def maximum_version(self):
return TLSVersion(super().maximum_version)

@maximum_version.setter
def maximum_version(self, value):
super(SSLContext, SSLContext).maximum_version.__set__(self, value)

@property
def options(self):
return Options(super().options)
Expand Down
117 changes: 117 additions & 0 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,69 @@ def test_hostname_checks_common_name(self):
with self.assertRaises(AttributeError):
ctx.hostname_checks_common_name = True

@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
"required OpenSSL 1.1.0g")
def test_min_max_version(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
self.assertEqual(
ctx.minimum_version, ssl.TLSVersion.MINIMUM_SUPPORTED
)
self.assertEqual(
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
)

ctx.minimum_version = ssl.TLSVersion.TLSv1_1
ctx.maximum_version = ssl.TLSVersion.TLSv1_2
self.assertEqual(
ctx.minimum_version, ssl.TLSVersion.TLSv1_1
)
self.assertEqual(
ctx.maximum_version, ssl.TLSVersion.TLSv1_2
)

ctx.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED
ctx.maximum_version = ssl.TLSVersion.TLSv1
self.assertEqual(
ctx.minimum_version, ssl.TLSVersion.MINIMUM_SUPPORTED
)
self.assertEqual(
ctx.maximum_version, ssl.TLSVersion.TLSv1
)

ctx.maximum_version = ssl.TLSVersion.MAXIMUM_SUPPORTED
self.assertEqual(
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
)

ctx.maximum_version = ssl.TLSVersion.MINIMUM_SUPPORTED
self.assertIn(
ctx.maximum_version,
{ssl.TLSVersion.TLSv1, ssl.TLSVersion.SSLv3}
)

ctx.minimum_version = ssl.TLSVersion.MAXIMUM_SUPPORTED
self.assertIn(
ctx.minimum_version,
{ssl.TLSVersion.TLSv1_2, ssl.TLSVersion.TLSv1_3}
)

with self.assertRaises(ValueError):
ctx.minimum_version = 42

ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)

self.assertEqual(
ctx.minimum_version, ssl.TLSVersion.MINIMUM_SUPPORTED
)
self.assertEqual(
ctx.maximum_version, ssl.TLSVersion.MAXIMUM_SUPPORTED
)
with self.assertRaises(ValueError):
ctx.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED
with self.assertRaises(ValueError):
ctx.maximum_version = ssl.TLSVersion.TLSv1


@unittest.skipUnless(have_verify_flags(),
"verify_flags need OpenSSL > 0.9.8")
def test_verify_flags(self):
Expand Down Expand Up @@ -3457,6 +3520,60 @@ def test_tls1_3(self):
})
self.assertEqual(s.version(), 'TLSv1.3')

@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
"required OpenSSL 1.1.0g")
def test_min_max_version(self):
client_context, server_context, hostname = testing_context()
# client TLSv1.0 to 1.2
client_context.minimum_version = ssl.TLSVersion.TLSv1
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
# server only TLSv1.2
server_context.minimum_version = ssl.TLSVersion.TLSv1_2
server_context.maximum_version = ssl.TLSVersion.TLSv1_2

with ThreadedEchoServer(context=server_context) as server:
with client_context.wrap_socket(socket.socket(),
server_hostname=hostname) as s:
s.connect((HOST, server.port))
self.assertEqual(s.version(), 'TLSv1.2')

# client 1.0 to 1.2, server 1.0 to 1.1
server_context.minimum_version = ssl.TLSVersion.TLSv1
server_context.maximum_version = ssl.TLSVersion.TLSv1_1

with ThreadedEchoServer(context=server_context) as server:
with client_context.wrap_socket(socket.socket(),
server_hostname=hostname) as s:
s.connect((HOST, server.port))
self.assertEqual(s.version(), 'TLSv1.1')

# client 1.0, server 1.2 (mismatch)
server_context.minimum_version = ssl.TLSVersion.TLSv1_2
server_context.maximum_version = ssl.TLSVersion.TLSv1_2
client_context.minimum_version = ssl.TLSVersion.TLSv1
client_context.maximum_version = ssl.TLSVersion.TLSv1
with ThreadedEchoServer(context=server_context) as server:
with client_context.wrap_socket(socket.socket(),
server_hostname=hostname) as s:
with self.assertRaises(ssl.SSLError) as e:
s.connect((HOST, server.port))
self.assertIn("alert", str(e.exception))


@unittest.skipUnless(hasattr(ssl.SSLContext, 'minimum_version'),
"required OpenSSL 1.1.0g")
@unittest.skipUnless(ssl.HAS_SSLv3, "requires SSLv3 support")
def test_min_max_version_sslv3(self):
client_context, server_context, hostname = testing_context()
server_context.minimum_version = ssl.TLSVersion.SSLv3
client_context.minimum_version = ssl.TLSVersion.SSLv3
client_context.maximum_version = ssl.TLSVersion.SSLv3
with ThreadedEchoServer(context=server_context) as server:
with client_context.wrap_socket(socket.socket(),
server_hostname=hostname) as s:
s.connect((HOST, server.port))
self.assertEqual(s.version(), 'SSLv3')

@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
def test_default_ecdh_curve(self):
# Issue #21015: elliptic curve-based Diffie Hellman key exchange
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add TLSVersion constants and SSLContext.maximum_version / minimum_version
attributes. The new API wraps OpenSSL 1.1
https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_min_proto_version.html
feature.
Loading