Python-checkins
Threads by month
- ----- 2025 -----
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
February 2018
- 4 participants
- 401 discussions

Feb. 27, 2018
https://github.com/python/cpython/commit/4c842b09209ccf1b4f853106b1f58bb888…
commit: 4c842b09209ccf1b4f853106b1f58bb888da02ef
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T03:41:04-08:00
summary:
bpo-31453: Add setter for min/max protocol version (GH-5259)
OpenSSL 1.1 has introduced a new API to set the minimum and maximum
supported protocol version. The API is easier to use …
[View More]than the old
OP_NO_TLS1 option flags, too.
Since OpenSSL has no call to set minimum version to highest supported,
the implementation emulate maximum_version = MINIMUM_SUPPORTED and
minimum_version = MAXIMUM_SUPPORTED by figuring out the minumum and
maximum supported version at compile time.
Signed-off-by: Christian Heimes <christian(a)python.org>
(cherry picked from commit 698dde16f60729d9e3f53c23a4ddb8e5ffe818bf)
Co-authored-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
M Lib/ssl.py
M Lib/test/test_ssl.py
M Modules/_ssl.c
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index d18a505937a8..2b4bed413985 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -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.
@@ -871,7 +886,7 @@ 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
@@ -879,6 +894,36 @@ Constants
.. 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.
@@ -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
-----------
@@ -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.
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 2e5e7c178b41..d1eeec209e65 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -675,6 +675,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
------
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 75ebcc165a17..2db887354714 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -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(
@@ -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
@@ -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)
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index ca2357e98e3e..8d98b805b49a 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -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):
@@ -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
diff --git a/Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst b/Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst
new file mode 100644
index 000000000000..6d43dfd8207d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst
@@ -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.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index f50823e6947a..f9e061dfe01d 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -320,6 +320,53 @@ enum py_ssl_version {
PY_SSL_VERSION_TLS_SERVER,
};
+enum py_proto_version {
+ PY_PROTO_MINIMUM_SUPPORTED = -2,
+ PY_PROTO_SSLv3 = SSL3_VERSION,
+ PY_PROTO_TLSv1 = TLS1_VERSION,
+ PY_PROTO_TLSv1_1 = TLS1_1_VERSION,
+ PY_PROTO_TLSv1_2 = TLS1_2_VERSION,
+#ifdef TLS1_3_VERSION
+ PY_PROTO_TLSv1_3 = TLS1_3_VERSION,
+#else
+ PY_PROTO_TLSv1_3 = 0x304,
+#endif
+ PY_PROTO_MAXIMUM_SUPPORTED = -1,
+
+/* OpenSSL has no dedicated API to set the minimum version to the maximum
+ * available version, and the other way around. We have to figure out the
+ * minimum and maximum available version on our own and hope for the best.
+ */
+#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_SSLv3,
+#elif defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1,
+#elif defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1_1,
+#elif defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1_2,
+#elif defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1_3,
+#else
+ #error "PY_PROTO_MINIMUM_AVAILABLE not found"
+#endif
+
+#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1_3,
+#elif defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1_2,
+#elif defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1_1,
+#elif defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1,
+#elif defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_SSLv3,
+#else
+ #error "PY_PROTO_MAXIMUM_AVAILABLE not found"
+#endif
+};
+
+
/* serves as a flag to see whether we've initialized the SSL thread support. */
/* 0 means no, greater than 0 means yes */
@@ -3323,6 +3370,106 @@ set_verify_flags(PySSLContext *self, PyObject *arg, void *c)
return 0;
}
+/* Getter and setter for protocol version */
+#if defined(SSL_CTRL_GET_MAX_PROTO_VERSION)
+
+
+static int
+set_min_max_proto_version(PySSLContext *self, PyObject *arg, int what)
+{
+ long v;
+ int result;
+
+ if (!PyArg_Parse(arg, "l", &v))
+ return -1;
+ if (v > INT_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "Option is too long");
+ return -1;
+ }
+
+ switch(self->protocol) {
+ case PY_SSL_VERSION_TLS_CLIENT: /* fall through */
+ case PY_SSL_VERSION_TLS_SERVER: /* fall through */
+ case PY_SSL_VERSION_TLS:
+ break;
+ default:
+ PyErr_SetString(
+ PyExc_ValueError,
+ "The context's protocol doesn't support modification of "
+ "highest and lowest version."
+ );
+ return -1;
+ }
+
+ if (what == 0) {
+ switch(v) {
+ case PY_PROTO_MINIMUM_SUPPORTED:
+ v = 0;
+ break;
+ case PY_PROTO_MAXIMUM_SUPPORTED:
+ /* Emulate max for set_min_proto_version */
+ v = PY_PROTO_MAXIMUM_AVAILABLE;
+ break;
+ default:
+ break;
+ }
+ result = SSL_CTX_set_min_proto_version(self->ctx, v);
+ }
+ else {
+ switch(v) {
+ case PY_PROTO_MAXIMUM_SUPPORTED:
+ v = 0;
+ break;
+ case PY_PROTO_MINIMUM_SUPPORTED:
+ /* Emulate max for set_min_proto_version */
+ v = PY_PROTO_MINIMUM_AVAILABLE;
+ break;
+ default:
+ break;
+ }
+ result = SSL_CTX_set_max_proto_version(self->ctx, v);
+ }
+ if (result == 0) {
+ PyErr_Format(PyExc_ValueError,
+ "Unsupported protocol version 0x%x", v);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *
+get_minimum_version(PySSLContext *self, void *c)
+{
+ int v = SSL_CTX_ctrl(self->ctx, SSL_CTRL_GET_MIN_PROTO_VERSION, 0, NULL);
+ if (v == 0) {
+ v = PY_PROTO_MINIMUM_SUPPORTED;
+ }
+ return PyLong_FromLong(v);
+}
+
+static int
+set_minimum_version(PySSLContext *self, PyObject *arg, void *c)
+{
+ return set_min_max_proto_version(self, arg, 0);
+}
+
+static PyObject *
+get_maximum_version(PySSLContext *self, void *c)
+{
+ int v = SSL_CTX_ctrl(self->ctx, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, NULL);
+ if (v == 0) {
+ v = PY_PROTO_MAXIMUM_SUPPORTED;
+ }
+ return PyLong_FromLong(v);
+}
+
+static int
+set_maximum_version(PySSLContext *self, PyObject *arg, void *c)
+{
+ return set_min_max_proto_version(self, arg, 1);
+}
+#endif /* SSL_CTRL_GET_MAX_PROTO_VERSION */
+
static PyObject *
get_options(PySSLContext *self, void *c)
{
@@ -4289,8 +4436,14 @@ static PyGetSetDef context_getsetlist[] = {
(setter) set_check_hostname, NULL},
{"_host_flags", (getter) get_host_flags,
(setter) set_host_flags, NULL},
+#if SSL_CTRL_GET_MAX_PROTO_VERSION
+ {"minimum_version", (getter) get_minimum_version,
+ (setter) set_minimum_version, NULL},
+ {"maximum_version", (getter) get_maximum_version,
+ (setter) set_maximum_version, NULL},
+#endif
{"sni_callback", (getter) get_sni_callback,
- (setter) set_sni_callback, PySSLContext_sni_callback_doc},
+ (setter) set_sni_callback, PySSLContext_sni_callback_doc},
{"options", (getter) get_options,
(setter) set_options, NULL},
{"protocol", (getter) get_protocol,
@@ -5711,45 +5864,82 @@ PyInit__ssl(void)
X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS);
#endif
+ /* protocol versions */
+ PyModule_AddIntConstant(m, "PROTO_MINIMUM_SUPPORTED",
+ PY_PROTO_MINIMUM_SUPPORTED);
+ PyModule_AddIntConstant(m, "PROTO_MAXIMUM_SUPPORTED",
+ PY_PROTO_MAXIMUM_SUPPORTED);
+ PyModule_AddIntConstant(m, "PROTO_SSLv3", PY_PROTO_SSLv3);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1", PY_PROTO_TLSv1);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1_1", PY_PROTO_TLSv1_1);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1_2", PY_PROTO_TLSv1_2);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1_3", PY_PROTO_TLSv1_3);
+
+#define addbool(m, v, b) \
+ Py_INCREF((b) ? Py_True : Py_False); \
+ PyModule_AddObject((m), (v), (b) ? Py_True : Py_False);
+
#if HAVE_SNI
- r = Py_True;
+ addbool(m, "HAS_SNI", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_SNI", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_SNI", r);
-#ifdef OPENSSL_NO_ECDH
- r = Py_False;
+ addbool(m, "HAS_TLS_UNIQUE", 1);
+
+#ifndef OPENSSL_NO_ECDH
+ addbool(m, "HAS_ECDH", 1);
#else
- r = Py_True;
+ addbool(m, "HAS_ECDH", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_ECDH", r);
#if HAVE_NPN
- r = Py_True;
+ addbool(m, "HAS_NPN", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_NPN", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_NPN", r);
#if HAVE_ALPN
- r = Py_True;
+ addbool(m, "HAS_ALPN", 1);
+#else
+ addbool(m, "HAS_ALPN", 0);
+#endif
+
+#if defined(SSL2_VERSION) && !defined(OPENSSL_NO_SSL2)
+ addbool(m, "HAS_SSLv2", 1);
+#else
+ addbool(m, "HAS_SSLv2", 0);
+#endif
+
+#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
+ addbool(m, "HAS_SSLv3", 1);
+#else
+ addbool(m, "HAS_SSLv3", 0);
+#endif
+
+#if defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1)
+ addbool(m, "HAS_TLSv1", 1);
+#else
+ addbool(m, "HAS_TLSv1", 0);
+#endif
+
+#if defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1)
+ addbool(m, "HAS_TLSv1_1", 1);
+#else
+ addbool(m, "HAS_TLSv1_1", 0);
+#endif
+
+#if defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2)
+ addbool(m, "HAS_TLSv1_2", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_TLSv1_2", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_ALPN", r);
#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
- r = Py_True;
+ addbool(m, "HAS_TLSv1_3", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_TLSv1_3", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_TLSv1_3", r);
/* Mappings for error codes */
err_codes_to_names = PyDict_New();
[View Less]
1
0

Feb. 27, 2018
https://github.com/python/cpython/commit/698dde16f60729d9e3f53c23a4ddb8e5ff…
commit: 698dde16f60729d9e3f53c23a4ddb8e5ffe818bf
branch: master
author: Christian Heimes <christian(a)python.org>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T11:54:43+01:00
summary:
bpo-31453: Add setter for min/max protocol version (#5259)
OpenSSL 1.1 has introduced a new API to set the minimum and maximum
supported protocol version. The API is easier to use than the old
OP_NO_TLS1 option …
[View More]flags, too.
Since OpenSSL has no call to set minimum version to highest supported,
the implementation emulate maximum_version = MINIMUM_SUPPORTED and
minimum_version = MAXIMUM_SUPPORTED by figuring out the minumum and
maximum supported version at compile time.
Signed-off-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
M Lib/ssl.py
M Lib/test/test_ssl.py
M Modules/_ssl.c
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index d18a505937a8..2b4bed413985 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -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.
@@ -871,7 +886,7 @@ 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
@@ -879,6 +894,36 @@ Constants
.. 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.
@@ -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
-----------
@@ -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.
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 2d62ffa5004c..fa2d472820c9 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -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
------
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 75ebcc165a17..2db887354714 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -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(
@@ -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
@@ -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)
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index ca2357e98e3e..8d98b805b49a 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -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):
@@ -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
diff --git a/Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst b/Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst
new file mode 100644
index 000000000000..6d43dfd8207d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-01-21-15-01-50.bpo-31453.cZiZBe.rst
@@ -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.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index f50823e6947a..f9e061dfe01d 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -320,6 +320,53 @@ enum py_ssl_version {
PY_SSL_VERSION_TLS_SERVER,
};
+enum py_proto_version {
+ PY_PROTO_MINIMUM_SUPPORTED = -2,
+ PY_PROTO_SSLv3 = SSL3_VERSION,
+ PY_PROTO_TLSv1 = TLS1_VERSION,
+ PY_PROTO_TLSv1_1 = TLS1_1_VERSION,
+ PY_PROTO_TLSv1_2 = TLS1_2_VERSION,
+#ifdef TLS1_3_VERSION
+ PY_PROTO_TLSv1_3 = TLS1_3_VERSION,
+#else
+ PY_PROTO_TLSv1_3 = 0x304,
+#endif
+ PY_PROTO_MAXIMUM_SUPPORTED = -1,
+
+/* OpenSSL has no dedicated API to set the minimum version to the maximum
+ * available version, and the other way around. We have to figure out the
+ * minimum and maximum available version on our own and hope for the best.
+ */
+#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_SSLv3,
+#elif defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1,
+#elif defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1_1,
+#elif defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1_2,
+#elif defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
+ PY_PROTO_MINIMUM_AVAILABLE = PY_PROTO_TLSv1_3,
+#else
+ #error "PY_PROTO_MINIMUM_AVAILABLE not found"
+#endif
+
+#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1_3,
+#elif defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1_2,
+#elif defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1_1,
+#elif defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_TLSv1,
+#elif defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
+ PY_PROTO_MAXIMUM_AVAILABLE = PY_PROTO_SSLv3,
+#else
+ #error "PY_PROTO_MAXIMUM_AVAILABLE not found"
+#endif
+};
+
+
/* serves as a flag to see whether we've initialized the SSL thread support. */
/* 0 means no, greater than 0 means yes */
@@ -3323,6 +3370,106 @@ set_verify_flags(PySSLContext *self, PyObject *arg, void *c)
return 0;
}
+/* Getter and setter for protocol version */
+#if defined(SSL_CTRL_GET_MAX_PROTO_VERSION)
+
+
+static int
+set_min_max_proto_version(PySSLContext *self, PyObject *arg, int what)
+{
+ long v;
+ int result;
+
+ if (!PyArg_Parse(arg, "l", &v))
+ return -1;
+ if (v > INT_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "Option is too long");
+ return -1;
+ }
+
+ switch(self->protocol) {
+ case PY_SSL_VERSION_TLS_CLIENT: /* fall through */
+ case PY_SSL_VERSION_TLS_SERVER: /* fall through */
+ case PY_SSL_VERSION_TLS:
+ break;
+ default:
+ PyErr_SetString(
+ PyExc_ValueError,
+ "The context's protocol doesn't support modification of "
+ "highest and lowest version."
+ );
+ return -1;
+ }
+
+ if (what == 0) {
+ switch(v) {
+ case PY_PROTO_MINIMUM_SUPPORTED:
+ v = 0;
+ break;
+ case PY_PROTO_MAXIMUM_SUPPORTED:
+ /* Emulate max for set_min_proto_version */
+ v = PY_PROTO_MAXIMUM_AVAILABLE;
+ break;
+ default:
+ break;
+ }
+ result = SSL_CTX_set_min_proto_version(self->ctx, v);
+ }
+ else {
+ switch(v) {
+ case PY_PROTO_MAXIMUM_SUPPORTED:
+ v = 0;
+ break;
+ case PY_PROTO_MINIMUM_SUPPORTED:
+ /* Emulate max for set_min_proto_version */
+ v = PY_PROTO_MINIMUM_AVAILABLE;
+ break;
+ default:
+ break;
+ }
+ result = SSL_CTX_set_max_proto_version(self->ctx, v);
+ }
+ if (result == 0) {
+ PyErr_Format(PyExc_ValueError,
+ "Unsupported protocol version 0x%x", v);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *
+get_minimum_version(PySSLContext *self, void *c)
+{
+ int v = SSL_CTX_ctrl(self->ctx, SSL_CTRL_GET_MIN_PROTO_VERSION, 0, NULL);
+ if (v == 0) {
+ v = PY_PROTO_MINIMUM_SUPPORTED;
+ }
+ return PyLong_FromLong(v);
+}
+
+static int
+set_minimum_version(PySSLContext *self, PyObject *arg, void *c)
+{
+ return set_min_max_proto_version(self, arg, 0);
+}
+
+static PyObject *
+get_maximum_version(PySSLContext *self, void *c)
+{
+ int v = SSL_CTX_ctrl(self->ctx, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, NULL);
+ if (v == 0) {
+ v = PY_PROTO_MAXIMUM_SUPPORTED;
+ }
+ return PyLong_FromLong(v);
+}
+
+static int
+set_maximum_version(PySSLContext *self, PyObject *arg, void *c)
+{
+ return set_min_max_proto_version(self, arg, 1);
+}
+#endif /* SSL_CTRL_GET_MAX_PROTO_VERSION */
+
static PyObject *
get_options(PySSLContext *self, void *c)
{
@@ -4289,8 +4436,14 @@ static PyGetSetDef context_getsetlist[] = {
(setter) set_check_hostname, NULL},
{"_host_flags", (getter) get_host_flags,
(setter) set_host_flags, NULL},
+#if SSL_CTRL_GET_MAX_PROTO_VERSION
+ {"minimum_version", (getter) get_minimum_version,
+ (setter) set_minimum_version, NULL},
+ {"maximum_version", (getter) get_maximum_version,
+ (setter) set_maximum_version, NULL},
+#endif
{"sni_callback", (getter) get_sni_callback,
- (setter) set_sni_callback, PySSLContext_sni_callback_doc},
+ (setter) set_sni_callback, PySSLContext_sni_callback_doc},
{"options", (getter) get_options,
(setter) set_options, NULL},
{"protocol", (getter) get_protocol,
@@ -5711,45 +5864,82 @@ PyInit__ssl(void)
X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS);
#endif
+ /* protocol versions */
+ PyModule_AddIntConstant(m, "PROTO_MINIMUM_SUPPORTED",
+ PY_PROTO_MINIMUM_SUPPORTED);
+ PyModule_AddIntConstant(m, "PROTO_MAXIMUM_SUPPORTED",
+ PY_PROTO_MAXIMUM_SUPPORTED);
+ PyModule_AddIntConstant(m, "PROTO_SSLv3", PY_PROTO_SSLv3);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1", PY_PROTO_TLSv1);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1_1", PY_PROTO_TLSv1_1);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1_2", PY_PROTO_TLSv1_2);
+ PyModule_AddIntConstant(m, "PROTO_TLSv1_3", PY_PROTO_TLSv1_3);
+
+#define addbool(m, v, b) \
+ Py_INCREF((b) ? Py_True : Py_False); \
+ PyModule_AddObject((m), (v), (b) ? Py_True : Py_False);
+
#if HAVE_SNI
- r = Py_True;
+ addbool(m, "HAS_SNI", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_SNI", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_SNI", r);
-#ifdef OPENSSL_NO_ECDH
- r = Py_False;
+ addbool(m, "HAS_TLS_UNIQUE", 1);
+
+#ifndef OPENSSL_NO_ECDH
+ addbool(m, "HAS_ECDH", 1);
#else
- r = Py_True;
+ addbool(m, "HAS_ECDH", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_ECDH", r);
#if HAVE_NPN
- r = Py_True;
+ addbool(m, "HAS_NPN", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_NPN", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_NPN", r);
#if HAVE_ALPN
- r = Py_True;
+ addbool(m, "HAS_ALPN", 1);
+#else
+ addbool(m, "HAS_ALPN", 0);
+#endif
+
+#if defined(SSL2_VERSION) && !defined(OPENSSL_NO_SSL2)
+ addbool(m, "HAS_SSLv2", 1);
+#else
+ addbool(m, "HAS_SSLv2", 0);
+#endif
+
+#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3)
+ addbool(m, "HAS_SSLv3", 1);
+#else
+ addbool(m, "HAS_SSLv3", 0);
+#endif
+
+#if defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1)
+ addbool(m, "HAS_TLSv1", 1);
+#else
+ addbool(m, "HAS_TLSv1", 0);
+#endif
+
+#if defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1)
+ addbool(m, "HAS_TLSv1_1", 1);
+#else
+ addbool(m, "HAS_TLSv1_1", 0);
+#endif
+
+#if defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2)
+ addbool(m, "HAS_TLSv1_2", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_TLSv1_2", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_ALPN", r);
#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
- r = Py_True;
+ addbool(m, "HAS_TLSv1_3", 1);
#else
- r = Py_False;
+ addbool(m, "HAS_TLSv1_3", 0);
#endif
- Py_INCREF(r);
- PyModule_AddObject(m, "HAS_TLSv1_3", r);
/* Mappings for error codes */
err_codes_to_names = PyDict_New();
[View Less]
1
0

[3.7] bpo-32951: Disable SSLSocket/SSLObject constructor (GH-5864) (#5925)
by Christian Heimes Feb. 27, 2018
by Christian Heimes Feb. 27, 2018
Feb. 27, 2018
https://github.com/python/cpython/commit/89c2051a554d2053ac87b0adbf11ed0f1b…
commit: 89c2051a554d2053ac87b0adbf11ed0f1bb65db3
branch: 3.7
author: Christian Heimes <christian(a)python.org>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T11:17:32+01:00
summary:
[3.7] bpo-32951: Disable SSLSocket/SSLObject constructor (GH-5864) (#5925)
Direct instantiation of SSLSocket and SSLObject objects is now prohibited.
The constructors were never documented, tested, or designed as …
[View More]public
constructors. The SSLSocket constructor had limitations. For example it was
not possible to enabled hostname verification except was
ssl_version=PROTOCOL_TLS_CLIENT with cert_reqs=CERT_REQUIRED.
SSLContext.wrap_socket() and SSLContext.wrap_bio are the recommended API
to construct SSLSocket and SSLObject instances. ssl.wrap_socket() is
also deprecated.
The only test case for direct instantiation was added a couple of days
ago for IDNA testing.
Signed-off-by: Christian Heimes <christian(a)python.org>
(cherry picked from commit 9d50ab563df6307cabbcc9883cb8c52c614b0f22)
Co-authored-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
M Lib/ssl.py
M Lib/test/test_ssl.py
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 4889a7130aae..d18a505937a8 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -998,7 +998,7 @@ SSL Sockets
the specification of normal, OS-level sockets. See especially the
:ref:`notes on non-blocking sockets <ssl-nonblocking>`.
- :class:`SSLSocket` are not created directly, but using the
+ Instances of :class:`SSLSocket` must be created using the
:meth:`SSLContext.wrap_socket` method.
.. versionchanged:: 3.5
@@ -1013,6 +1013,11 @@ SSL Sockets
It is deprecated to create a :class:`SSLSocket` instance directly, use
:meth:`SSLContext.wrap_socket` to wrap a socket.
+ .. versionchanged:: 3.7
+ :class:`SSLSocket` instances must to created with
+ :meth:`~SSLContext.wrap_socket`. In earlier versions, it was possible
+ to create instances directly. This was never documented or officially
+ supported.
SSL sockets also have the following additional methods and attributes:
@@ -2249,11 +2254,12 @@ provided.
but does not provide any network IO itself. IO needs to be performed through
separate "BIO" objects which are OpenSSL's IO abstraction layer.
- An :class:`SSLObject` instance can be created using the
- :meth:`~SSLContext.wrap_bio` method. This method will create the
- :class:`SSLObject` instance and bind it to a pair of BIOs. The *incoming*
- BIO is used to pass data from Python to the SSL protocol instance, while the
- *outgoing* BIO is used to pass data the other way around.
+ This class has no public constructor. An :class:`SSLObject` instance
+ must be created using the :meth:`~SSLContext.wrap_bio` method. This
+ method will create the :class:`SSLObject` instance and bind it to a
+ pair of BIOs. The *incoming* BIO is used to pass data from Python to the
+ SSL protocol instance, while the *outgoing* BIO is used to pass data the
+ other way around.
The following methods are available:
@@ -2305,6 +2311,12 @@ provided.
:meth:`~SSLContext.wrap_socket`. An :class:`SSLObject` is always created
via an :class:`SSLContext`.
+ .. versionchanged:: 3.7
+ :class:`SSLObject` instances must to created with
+ :meth:`~SSLContext.wrap_bio`. In earlier versions, it was possible to
+ create instances directly. This was never documented or officially
+ supported.
+
An SSLObject communicates with the outside world using memory buffers. The
class :class:`MemoryBIO` provides a memory buffer that can be used for this
purpose. It wraps an OpenSSL memory BIO (Basic IO) object:
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 048e87dda12f..2e5e7c178b41 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -669,6 +669,12 @@ OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
recommend :meth:`~ssl.SSLContext.wrap_socket` instead.
(Contributed by Christian Heimes in :issue:`28124`.)
+:class:`~ssl.SSLSocket` and :class:`~ssl.SSLObject` no longer have a public
+constructor. Direct instantiation was never a documented and supported
+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`)
+
string
------
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 94ea35e358a3..75ebcc165a17 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -390,24 +390,24 @@ def wrap_socket(self, sock, server_side=False,
server_hostname=None, session=None):
# SSLSocket class handles server_hostname encoding before it calls
# ctx._wrap_socket()
- return self.sslsocket_class(
+ return self.sslsocket_class._create(
sock=sock,
server_side=server_side,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs,
server_hostname=server_hostname,
- _context=self,
- _session=session
+ context=self,
+ session=session
)
def wrap_bio(self, incoming, outgoing, server_side=False,
server_hostname=None, session=None):
# Need to encode server_hostname here because _wrap_bio() can only
# handle ASCII str.
- return self.sslobject_class(
+ return self.sslobject_class._create(
incoming, outgoing, server_side=server_side,
server_hostname=self._encode_hostname(server_hostname),
- session=session, _context=self,
+ session=session, context=self,
)
def set_npn_protocols(self, npn_protocols):
@@ -612,14 +612,23 @@ class SSLObject:
* Any form of network IO incluging methods such as ``recv`` and ``send``.
* The ``do_handshake_on_connect`` and ``suppress_ragged_eofs`` machinery.
"""
+ def __init__(self, *args, **kwargs):
+ raise TypeError(
+ f"{self.__class__.__name__} does not have a public "
+ f"constructor. Instances are returned by SSLContext.wrap_bio()."
+ )
- def __init__(self, incoming, outgoing, server_side=False,
- server_hostname=None, session=None, _context=None):
- self._sslobj = _context._wrap_bio(
+ @classmethod
+ def _create(cls, incoming, outgoing, server_side=False,
+ server_hostname=None, session=None, context=None):
+ self = cls.__new__(cls)
+ sslobj = context._wrap_bio(
incoming, outgoing, server_side=server_side,
server_hostname=server_hostname,
owner=self, session=session
)
+ self._sslobj = sslobj
+ return self
@property
def context(self):
@@ -741,72 +750,48 @@ def version(self):
class SSLSocket(socket):
"""This class implements a subtype of socket.socket that wraps
the underlying OS socket in an SSL context when necessary, and
- provides read and write methods over that channel."""
-
- def __init__(self, sock=None, keyfile=None, certfile=None,
- server_side=False, cert_reqs=CERT_NONE,
- ssl_version=PROTOCOL_TLS, ca_certs=None,
- do_handshake_on_connect=True,
- family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
- suppress_ragged_eofs=True, npn_protocols=None, ciphers=None,
- server_hostname=None,
- _context=None, _session=None):
-
- if _context:
- self._context = _context
- else:
- if server_side and not certfile:
- raise ValueError("certfile must be specified for server-side "
- "operations")
- if keyfile and not certfile:
- raise ValueError("certfile must be specified")
- if certfile and not keyfile:
- keyfile = certfile
- self._context = SSLContext(ssl_version)
- self._context.verify_mode = cert_reqs
- if ca_certs:
- self._context.load_verify_locations(ca_certs)
- if certfile:
- self._context.load_cert_chain(certfile, keyfile)
- if npn_protocols:
- self._context.set_npn_protocols(npn_protocols)
- if ciphers:
- self._context.set_ciphers(ciphers)
- self.keyfile = keyfile
- self.certfile = certfile
- self.cert_reqs = cert_reqs
- self.ssl_version = ssl_version
- self.ca_certs = ca_certs
- self.ciphers = ciphers
- # Can't use sock.type as other flags (such as SOCK_NONBLOCK) get
- # mixed in.
+ provides read and write methods over that channel. """
+
+ def __init__(self, *args, **kwargs):
+ raise TypeError(
+ f"{self.__class__.__name__} does not have a public "
+ f"constructor. Instances are returned by "
+ f"SSLContext.wrap_socket()."
+ )
+
+ @classmethod
+ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
+ suppress_ragged_eofs=True, server_hostname=None,
+ context=None, session=None):
if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM:
raise NotImplementedError("only stream sockets are supported")
if server_side:
if server_hostname:
raise ValueError("server_hostname can only be specified "
"in client mode")
- if _session is not None:
+ if session is not None:
raise ValueError("session can only be specified in "
"client mode")
- if self._context.check_hostname and not server_hostname:
+ if context.check_hostname and not server_hostname:
raise ValueError("check_hostname requires server_hostname")
- self._session = _session
+
+ kwargs = dict(
+ family=sock.family, type=sock.type, proto=sock.proto,
+ fileno=sock.fileno()
+ )
+ self = cls.__new__(cls, **kwargs)
+ super(SSLSocket, self).__init__(**kwargs)
+ self.settimeout(sock.gettimeout())
+ sock.detach()
+
+ self._context = context
+ self._session = session
+ self._closed = False
+ self._sslobj = None
self.server_side = server_side
- self.server_hostname = self._context._encode_hostname(server_hostname)
+ self.server_hostname = context._encode_hostname(server_hostname)
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
- if sock is not None:
- super().__init__(family=sock.family,
- type=sock.type,
- proto=sock.proto,
- fileno=sock.fileno())
- self.settimeout(sock.gettimeout())
- sock.detach()
- elif fileno is not None:
- super().__init__(fileno=fileno)
- else:
- super().__init__(family=family, type=type, proto=proto)
# See if we are connected
try:
@@ -818,8 +803,6 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
else:
connected = True
- self._closed = False
- self._sslobj = None
self._connected = connected
if connected:
# create the SSL object
@@ -834,10 +817,10 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
# non-blocking
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
self.do_handshake()
-
except (OSError, ValueError):
self.close()
raise
+ return self
@property
def context(self):
@@ -1184,12 +1167,25 @@ def wrap_socket(sock, keyfile=None, certfile=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
ciphers=None):
- return SSLSocket(sock=sock, keyfile=keyfile, certfile=certfile,
- server_side=server_side, cert_reqs=cert_reqs,
- ssl_version=ssl_version, ca_certs=ca_certs,
- do_handshake_on_connect=do_handshake_on_connect,
- suppress_ragged_eofs=suppress_ragged_eofs,
- ciphers=ciphers)
+
+ if server_side and not certfile:
+ raise ValueError("certfile must be specified for server-side "
+ "operations")
+ if keyfile and not certfile:
+ raise ValueError("certfile must be specified")
+ context = SSLContext(ssl_version)
+ context.verify_mode = cert_reqs
+ if ca_certs:
+ context.load_verify_locations(ca_certs)
+ if certfile:
+ context.load_cert_chain(certfile, keyfile)
+ if ciphers:
+ context.set_ciphers(ciphers)
+ return context.wrap_socket(
+ sock=sock, server_side=server_side,
+ do_handshake_on_connect=do_handshake_on_connect,
+ suppress_ragged_eofs=suppress_ragged_eofs
+ )
# some utility functions
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index d978a9e1b0fe..ca2357e98e3e 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -263,6 +263,11 @@ def test_constants(self):
ssl.OP_NO_TLSv1_2
self.assertEqual(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv23)
+ def test_private_init(self):
+ with self.assertRaisesRegex(TypeError, "public constructor"):
+ with socket.socket() as s:
+ ssl.SSLSocket(s)
+
def test_str_for_enums(self):
# Make sure that the PROTOCOL_* constants have enum-like string
# reprs.
@@ -1657,6 +1662,13 @@ def test_error_types(self):
self.assertRaises(TypeError, bio.write, 1)
+class SSLObjectTests(unittest.TestCase):
+ def test_private_init(self):
+ bio = ssl.MemoryBIO()
+ with self.assertRaisesRegex(TypeError, "public constructor"):
+ ssl.SSLObject(bio, bio)
+
+
class SimpleBackgroundTests(unittest.TestCase):
"""Tests that connect to a simple server running in the background"""
@@ -2735,12 +2747,6 @@ def test_check_hostname_idn(self):
self.assertEqual(s.server_hostname, expected_hostname)
self.assertTrue(cert, "Can't get peer certificate.")
- with ssl.SSLSocket(socket.socket(),
- server_hostname=server_hostname) as s:
- s.connect((HOST, server.port))
- s.getpeercert()
- self.assertEqual(s.server_hostname, expected_hostname)
-
# incorrect hostname should raise an exception
server = ThreadedEchoServer(context=server_context, chatty=True)
with server:
@@ -3999,7 +4005,7 @@ def test_main(verbose=False):
tests = [
ContextTests, BasicSocketTests, SSLErrorTests, MemoryBIOTests,
- SimpleBackgroundTests, ThreadedTests,
+ SSLObjectTests, SimpleBackgroundTests, ThreadedTests,
]
if support.is_resource_enabled('network'):
diff --git a/Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst b/Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst
new file mode 100644
index 000000000000..9c038cf25979
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst
@@ -0,0 +1,3 @@
+Direct instantiation of SSLSocket and SSLObject objects is now prohibited.
+The constructors were never documented, tested, or designed as public
+constructors. Users were suppose to use ssl.wrap_socket() or SSLContext.
[View Less]
1
0

Feb. 27, 2018
https://github.com/python/cpython/commit/102d5204add249248d1a0fa1dd3f673e88…
commit: 102d5204add249248d1a0fa1dd3f673e884b06b4
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T01:45:31-08:00
summary:
bpo-28124: deprecate ssl.wrap_socket() (GH-5888)
The ssl module function ssl.wrap_socket() has been de-emphasized
and deprecated in favor of the more secure and efficient
SSLContext.…
[View More]wrap_socket() method.
Signed-off-by: Christian Heimes <christian(a)python.org>
(cherry picked from commit 90f05a527c7d439f1d0cba80f2eb32e60ee20fc3)
Co-authored-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 5d5232eda30c..4889a7130aae 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -59,6 +59,125 @@ by SSL sockets created through the :meth:`SSLContext.wrap_socket` method.
Functions, Constants, and Exceptions
------------------------------------
+
+Socket creation
+^^^^^^^^^^^^^^^
+
+Since Python 3.2 and 2.7.9, it is recommended to use the
+:meth:`SSLContext.wrap_socket` of an :class:`SSLContext` instance to wrap
+sockets as :class:`SSLSocket` objects. The helper functions
+:func:`create_default_context` returns a new context with secure default
+settings. The old :func:`wrap_socket` function is deprecated since it is
+both inefficient and has no support for server name indication (SNI) and
+hostname matching.
+
+Client socket example with default context and IPv4/IPv6 dual stack::
+
+ import socket
+ import ssl
+
+ hostname = 'www.python.org'
+ context = ssl.create_default_context()
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+Client socket example with custom context and IPv4::
+
+ hostname = 'www.python.org'
+ # PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.load_verify_locations('path/to/cabundle.pem')
+
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+Server socket example listening on localhost IPv4::
+
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain('/path/to/certchain.pem', '/path/to/private.key')
+
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
+ sock.bind(('127.0.0.1', 8443))
+ sock.listen(5)
+ with context.wrap_socket(sock, server_side=True) as ssock:
+ conn, addr = ssock.accept()
+ ...
+
+
+Context creation
+^^^^^^^^^^^^^^^^
+
+A convenience function helps create :class:`SSLContext` objects for common
+purposes.
+
+.. function:: create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
+
+ Return a new :class:`SSLContext` object with default settings for
+ the given *purpose*. The settings are chosen by the :mod:`ssl` module,
+ and usually represent a higher security level than when calling the
+ :class:`SSLContext` constructor directly.
+
+ *cafile*, *capath*, *cadata* represent optional CA certificates to
+ trust for certificate verification, as in
+ :meth:`SSLContext.load_verify_locations`. If all three are
+ :const:`None`, this function can choose to trust the system's default
+ CA certificates instead.
+
+ The settings are: :data:`PROTOCOL_TLS`, :data:`OP_NO_SSLv2`, and
+ :data:`OP_NO_SSLv3` with high encryption cipher suites without RC4 and
+ without unauthenticated cipher suites. Passing :data:`~Purpose.SERVER_AUTH`
+ as *purpose* sets :data:`~SSLContext.verify_mode` to :data:`CERT_REQUIRED`
+ and either loads CA certificates (when at least one of *cafile*, *capath* or
+ *cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
+ default CA certificates.
+
+ .. note::
+ The protocol, options, cipher and other settings may change to more
+ restrictive values anytime without prior deprecation. The values
+ represent a fair balance between compatibility and security.
+
+ If your application needs specific settings, you should create a
+ :class:`SSLContext` and apply the settings yourself.
+
+ .. note::
+ If you find that when certain older clients or servers attempt to connect
+ with a :class:`SSLContext` created by this function that they get an error
+ stating "Protocol or cipher suite mismatch", it may be that they only
+ support SSL3.0 which this function excludes using the
+ :data:`OP_NO_SSLv3`. SSL3.0 is widely considered to be `completely broken
+ <https://en.wikipedia.org/wiki/POODLE>`_. If you still wish to continue to
+ use this function but still allow SSL 3.0 connections you can re-enable
+ them using::
+
+ ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
+ ctx.options &= ~ssl.OP_NO_SSLv3
+
+ .. versionadded:: 3.4
+
+ .. versionchanged:: 3.4.4
+
+ RC4 was dropped from the default cipher string.
+
+ .. versionchanged:: 3.6
+
+ ChaCha20/Poly1305 was added to the default cipher string.
+
+ 3DES was dropped from the default cipher string.
+
+ .. versionchanged:: 3.7
+
+ TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
+ and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
+
+
+Exceptions
+^^^^^^^^^^
+
.. exception:: SSLError
Raised to signal an error from the underlying SSL implementation
@@ -152,173 +271,6 @@ Functions, Constants, and Exceptions
The exception is now an alias for :exc:`SSLCertVerificationError`.
-Socket creation
-^^^^^^^^^^^^^^^
-
-The following function allows for standalone socket creation. Starting from
-Python 3.2, it can be more flexible to use :meth:`SSLContext.wrap_socket`
-instead.
-
-.. function:: wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)
-
- Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance
- of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps
- the underlying socket in an SSL context. ``sock`` must be a
- :data:`~socket.SOCK_STREAM` socket; other socket types are unsupported.
-
- For client-side sockets, the context construction is lazy; if the
- underlying socket isn't connected yet, the context construction will be
- performed after :meth:`connect` is called on the socket. For
- server-side sockets, if the socket has no remote peer, it is assumed
- to be a listening socket, and the server-side SSL wrapping is
- automatically performed on client connections accepted via the
- :meth:`accept` method. :func:`wrap_socket` may raise :exc:`SSLError`.
-
- The ``keyfile`` and ``certfile`` parameters specify optional files which
- contain a certificate to be used to identify the local side of the
- connection. See the discussion of :ref:`ssl-certificates` for more
- information on how the certificate is stored in the ``certfile``.
-
- The parameter ``server_side`` is a boolean which identifies whether
- server-side or client-side behavior is desired from this socket.
-
- The parameter ``cert_reqs`` specifies whether a certificate is required from
- the other side of the connection, and whether it will be validated if
- provided. It must be one of the three values :const:`CERT_NONE`
- (certificates ignored), :const:`CERT_OPTIONAL` (not required, but validated
- if provided), or :const:`CERT_REQUIRED` (required and validated). If the
- value of this parameter is not :const:`CERT_NONE`, then the ``ca_certs``
- parameter must point to a file of CA certificates.
-
- The ``ca_certs`` file contains a set of concatenated "certification
- authority" certificates, which are used to validate certificates passed from
- the other end of the connection. See the discussion of
- :ref:`ssl-certificates` for more information about how to arrange the
- certificates in this file.
-
- The parameter ``ssl_version`` specifies which version of the SSL protocol to
- use. Typically, the server chooses a particular protocol version, and the
- client must adapt to the server's choice. Most of the versions are not
- interoperable with the other versions. If not specified, the default is
- :data:`PROTOCOL_TLS`; it provides the most compatibility with other
- versions.
-
- Here's a table showing which versions in a client (down the side) can connect
- to which versions in a server (along the top):
-
- .. table::
-
- ======================== ============ ============ ============= ========= =========== ===========
- *client* / **server** **SSLv2** **SSLv3** **TLS** [3]_ **TLSv1** **TLSv1.1** **TLSv1.2**
- ------------------------ ------------ ------------ ------------- --------- ----------- -----------
- *SSLv2* yes no no [1]_ no no no
- *SSLv3* no yes no [2]_ no no no
- *TLS* (*SSLv23*) [3]_ no [1]_ no [2]_ yes yes yes yes
- *TLSv1* no no yes yes no no
- *TLSv1.1* no no yes no yes no
- *TLSv1.2* no no yes no no yes
- ======================== ============ ============ ============= ========= =========== ===========
-
- .. rubric:: Footnotes
- .. [1] :class:`SSLContext` disables SSLv2 with :data:`OP_NO_SSLv2` by default.
- .. [2] :class:`SSLContext` disables SSLv3 with :data:`OP_NO_SSLv3` by default.
- .. [3] TLS 1.3 protocol will be available with :data:`PROTOCOL_TLS` in
- OpenSSL >= 1.1.1. There is no dedicated PROTOCOL constant for just
- TLS 1.3.
-
- .. note::
-
- Which connections succeed will vary depending on the version of
- OpenSSL. For example, before OpenSSL 1.0.0, an SSLv23 client
- would always attempt SSLv2 connections.
-
- The *ciphers* parameter sets the available ciphers for this SSL object.
- It should be a string in the `OpenSSL cipher list format
- <https://wiki.openssl.org/index.php/Manual:Ciphers(1)#CIPHER_LIST_FORMAT>`_.
-
- The parameter ``do_handshake_on_connect`` specifies whether to do the SSL
- handshake automatically after doing a :meth:`socket.connect`, or whether the
- application program will call it explicitly, by invoking the
- :meth:`SSLSocket.do_handshake` method. Calling
- :meth:`SSLSocket.do_handshake` explicitly gives the program control over the
- blocking behavior of the socket I/O involved in the handshake.
-
- The parameter ``suppress_ragged_eofs`` specifies how the
- :meth:`SSLSocket.recv` method should signal unexpected EOF from the other end
- of the connection. If specified as :const:`True` (the default), it returns a
- normal EOF (an empty bytes object) in response to unexpected EOF errors
- raised from the underlying socket; if :const:`False`, it will raise the
- exceptions back to the caller.
-
- .. versionchanged:: 3.2
- New optional argument *ciphers*.
-
-Context creation
-^^^^^^^^^^^^^^^^
-
-A convenience function helps create :class:`SSLContext` objects for common
-purposes.
-
-.. function:: create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
-
- Return a new :class:`SSLContext` object with default settings for
- the given *purpose*. The settings are chosen by the :mod:`ssl` module,
- and usually represent a higher security level than when calling the
- :class:`SSLContext` constructor directly.
-
- *cafile*, *capath*, *cadata* represent optional CA certificates to
- trust for certificate verification, as in
- :meth:`SSLContext.load_verify_locations`. If all three are
- :const:`None`, this function can choose to trust the system's default
- CA certificates instead.
-
- The settings are: :data:`PROTOCOL_TLS`, :data:`OP_NO_SSLv2`, and
- :data:`OP_NO_SSLv3` with high encryption cipher suites without RC4 and
- without unauthenticated cipher suites. Passing :data:`~Purpose.SERVER_AUTH`
- as *purpose* sets :data:`~SSLContext.verify_mode` to :data:`CERT_REQUIRED`
- and either loads CA certificates (when at least one of *cafile*, *capath* or
- *cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
- default CA certificates.
-
- .. note::
- The protocol, options, cipher and other settings may change to more
- restrictive values anytime without prior deprecation. The values
- represent a fair balance between compatibility and security.
-
- If your application needs specific settings, you should create a
- :class:`SSLContext` and apply the settings yourself.
-
- .. note::
- If you find that when certain older clients or servers attempt to connect
- with a :class:`SSLContext` created by this function that they get an error
- stating "Protocol or cipher suite mismatch", it may be that they only
- support SSL3.0 which this function excludes using the
- :data:`OP_NO_SSLv3`. SSL3.0 is widely considered to be `completely broken
- <https://en.wikipedia.org/wiki/POODLE>`_. If you still wish to continue to
- use this function but still allow SSL 3.0 connections you can re-enable
- them using::
-
- ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
- ctx.options &= ~ssl.OP_NO_SSLv3
-
- .. versionadded:: 3.4
-
- .. versionchanged:: 3.4.4
-
- RC4 was dropped from the default cipher string.
-
- .. versionchanged:: 3.6
-
- ChaCha20/Poly1305 was added to the default cipher string.
-
- 3DES was dropped from the default cipher string.
-
- .. versionchanged:: 3.7
-
- TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
- and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
-
-
Random generation
^^^^^^^^^^^^^^^^^
@@ -474,9 +426,10 @@ Certificate handling
PEM-encoded string. If ``ssl_version`` is specified, uses that version of
the SSL protocol to attempt to connect to the server. If ``ca_certs`` is
specified, it should be a file containing a list of root certificates, the
- same format as used for the same parameter in :func:`wrap_socket`. The call
- will attempt to validate the server certificate against that set of root
- certificates, and will fail if the validation attempt fails.
+ same format as used for the same parameter in
+ :meth:`SSLContext.wrap_socket`. The call will attempt to validate the
+ server certificate against that set of root certificates, and will fail
+ if the validation attempt fails.
.. versionchanged:: 3.3
This function is now IPv6-compatible.
@@ -552,6 +505,33 @@ Certificate handling
.. versionadded:: 3.4
+.. function:: wrap_socket(sock, keyfile=None, certfile=None, \
+ server_side=False, cert_reqs=CERT_NONE, ssl_version=PROTOCOL_TLS, \
+ ca_certs=None, do_handshake_on_connect=True, \
+ suppress_ragged_eofs=True, ciphers=None)
+
+ Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance
+ of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps
+ the underlying socket in an SSL context. ``sock`` must be a
+ :data:`~socket.SOCK_STREAM` socket; other socket types are unsupported.
+
+ Internally, function creates a :class:`SSLContext` with protocol
+ *ssl_version* and :attr:`SSLContext.options` set to *cert_reqs*. If
+ parameters *keyfile*, *certfile*, *ca_certs* or *ciphers* are set, then
+ the values are passed to :meth:`SSLContext.load_cert_chain`,
+ :meth:`SSLContext.load_verify_locations`, and
+ :meth:`SSLContext.set_ciphers`.
+
+ The arguments *server_side*, *do_handshake_on_connect*, and
+ *suppress_ragged_eofs* have the same meaning as
+ :meth:`SSLContext.wrap_socket`.
+
+ .. deprecated:: 3.7
+
+ Since Python 3.2 and 2.7.9, it is recommended to use the
+ :meth:`SSLContext.wrap_socket` instead of :func:`wrap_socket`. The
+ top-level function is limited and creates an insecure client socket
+ without server name indication or hostname matching.
Constants
^^^^^^^^^
@@ -1018,7 +998,7 @@ SSL Sockets
the specification of normal, OS-level sockets. See especially the
:ref:`notes on non-blocking sockets <ssl-nonblocking>`.
- Usually, :class:`SSLSocket` are not created directly, but using the
+ :class:`SSLSocket` are not created directly, but using the
:meth:`SSLContext.wrap_socket` method.
.. versionchanged:: 3.5
@@ -1257,7 +1237,7 @@ SSL sockets also have the following additional methods and attributes:
.. attribute:: SSLSocket.context
The :class:`SSLContext` object this SSL socket is tied to. If the SSL
- socket was created using the top-level :func:`wrap_socket` function
+ socket was created using the deprecated :func:`wrap_socket` function
(rather than :meth:`SSLContext.wrap_socket`), this is a custom context
object created for this SSL socket.
@@ -1310,9 +1290,36 @@ to speed up repeated connections from the same clients.
.. class:: SSLContext(protocol=PROTOCOL_TLS)
Create a new SSL context. You may pass *protocol* which must be one
- of the ``PROTOCOL_*`` constants defined in this module.
- :data:`PROTOCOL_TLS` is currently recommended for maximum
- interoperability and default value.
+ of the ``PROTOCOL_*`` constants defined in this module. The parameter
+ specifies which version of the SSL protocol to use. Typically, the
+ server chooses a particular protocol version, and the client must adapt
+ to the server's choice. Most of the versions are not interoperable
+ with the other versions. If not specified, the default is
+ :data:`PROTOCOL_TLS`; it provides the most compatibility with other
+ versions.
+
+ Here's a table showing which versions in a client (down the side) can connect
+ to which versions in a server (along the top):
+
+ .. table::
+
+ ======================== ============ ============ ============= ========= =========== ===========
+ *client* / **server** **SSLv2** **SSLv3** **TLS** [3]_ **TLSv1** **TLSv1.1** **TLSv1.2**
+ ------------------------ ------------ ------------ ------------- --------- ----------- -----------
+ *SSLv2* yes no no [1]_ no no no
+ *SSLv3* no yes no [2]_ no no no
+ *TLS* (*SSLv23*) [3]_ no [1]_ no [2]_ yes yes yes yes
+ *TLSv1* no no yes yes no no
+ *TLSv1.1* no no yes no yes no
+ *TLSv1.2* no no yes no no yes
+ ======================== ============ ============ ============= ========= =========== ===========
+
+ .. rubric:: Footnotes
+ .. [1] :class:`SSLContext` disables SSLv2 with :data:`OP_NO_SSLv2` by default.
+ .. [2] :class:`SSLContext` disables SSLv3 with :data:`OP_NO_SSLv3` by default.
+ .. [3] TLS 1.3 protocol will be available with :data:`PROTOCOL_TLS` in
+ OpenSSL >= 1.1.1. There is no dedicated PROTOCOL constant for just
+ TLS 1.3.
.. seealso::
:func:`create_default_context` lets the :mod:`ssl` module choose
@@ -1645,14 +1652,21 @@ to speed up repeated connections from the same clients.
server_hostname=None, session=None)
Wrap an existing Python socket *sock* and return an instance of
- :attr:`SSLContext.sslsocket_class` (default :class:`SSLSocket`).
- *sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket
- types are unsupported.
+ :attr:`SSLContext.sslsocket_class` (default :class:`SSLSocket`). The
+ returned SSL socket is tied to the context, its settings and certificates.
+ *sock* must be a :data:`~socket.SOCK_STREAM` socket; other
+ socket types are unsupported.
- The returned SSL socket is tied to the context, its settings and
- certificates. The parameters *server_side*, *do_handshake_on_connect*
- and *suppress_ragged_eofs* have the same meaning as in the top-level
- :func:`wrap_socket` function.
+ The parameter ``server_side`` is a boolean which identifies whether
+ server-side or client-side behavior is desired from this socket.
+
+ For client-side sockets, the context construction is lazy; if the
+ underlying socket isn't connected yet, the context construction will be
+ performed after :meth:`connect` is called on the socket. For
+ server-side sockets, if the socket has no remote peer, it is assumed
+ to be a listening socket, and the server-side SSL wrapping is
+ automatically performed on client connections accepted via the
+ :meth:`accept` method. The method may raise :exc:`SSLError`.
On client connections, the optional parameter *server_hostname* specifies
the hostname of the service which we are connecting to. This allows a
@@ -1660,6 +1674,20 @@ to speed up repeated connections from the same clients.
quite similarly to HTTP virtual hosts. Specifying *server_hostname* will
raise a :exc:`ValueError` if *server_side* is true.
+ The parameter ``do_handshake_on_connect`` specifies whether to do the SSL
+ handshake automatically after doing a :meth:`socket.connect`, or whether the
+ application program will call it explicitly, by invoking the
+ :meth:`SSLSocket.do_handshake` method. Calling
+ :meth:`SSLSocket.do_handshake` explicitly gives the program control over the
+ blocking behavior of the socket I/O involved in the handshake.
+
+ The parameter ``suppress_ragged_eofs`` specifies how the
+ :meth:`SSLSocket.recv` method should signal unexpected EOF from the other end
+ of the connection. If specified as :const:`True` (the default), it returns a
+ normal EOF (an empty bytes object) in response to unexpected EOF errors
+ raised from the underlying socket; if :const:`False`, it will raise the
+ exceptions back to the caller.
+
*session*, see :attr:`~SSLSocket.session`.
.. versionchanged:: 3.5
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 4fad279d45e7..048e87dda12f 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -665,6 +665,11 @@ The ssl module has preliminary and experimental support for TLS 1.3 and
OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
:issue:`20995`, :issue:`29136`, and :issue:`30622`)
+:func:`~ssl.wrap_socket` is deprecated. Documentation has been updated to
+recommend :meth:`~ssl.SSLContext.wrap_socket` instead.
+(Contributed by Christian Heimes in :issue:`28124`.)
+
+
string
------
diff --git a/Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst b/Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst
new file mode 100644
index 000000000000..4f4ca001981d
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst
@@ -0,0 +1,3 @@
+The ssl module function ssl.wrap_socket() has been de-emphasized
+and deprecated in favor of the more secure and efficient
+SSLContext.wrap_socket() method.
[View Less]
1
0

Feb. 27, 2018
https://github.com/python/cpython/commit/9d50ab563df6307cabbcc9883cb8c52c61…
commit: 9d50ab563df6307cabbcc9883cb8c52c614b0f22
branch: master
author: Christian Heimes <christian(a)python.org>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T10:17:30+01:00
summary:
bpo-32951: Disable SSLSocket/SSLObject constructor (#5864)
Direct instantiation of SSLSocket and SSLObject objects is now prohibited.
The constructors were never documented, tested, or designed as public
…
[View More]constructors. The SSLSocket constructor had limitations. For example it was
not possible to enabled hostname verification except was
ssl_version=PROTOCOL_TLS_CLIENT with cert_reqs=CERT_REQUIRED.
SSLContext.wrap_socket() and SSLContext.wrap_bio are the recommended API
to construct SSLSocket and SSLObject instances. ssl.wrap_socket() is
also deprecated.
The only test case for direct instantiation was added a couple of days
ago for IDNA testing.
Signed-off-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
M Lib/ssl.py
M Lib/test/test_ssl.py
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 4889a7130aae..d18a505937a8 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -998,7 +998,7 @@ SSL Sockets
the specification of normal, OS-level sockets. See especially the
:ref:`notes on non-blocking sockets <ssl-nonblocking>`.
- :class:`SSLSocket` are not created directly, but using the
+ Instances of :class:`SSLSocket` must be created using the
:meth:`SSLContext.wrap_socket` method.
.. versionchanged:: 3.5
@@ -1013,6 +1013,11 @@ SSL Sockets
It is deprecated to create a :class:`SSLSocket` instance directly, use
:meth:`SSLContext.wrap_socket` to wrap a socket.
+ .. versionchanged:: 3.7
+ :class:`SSLSocket` instances must to created with
+ :meth:`~SSLContext.wrap_socket`. In earlier versions, it was possible
+ to create instances directly. This was never documented or officially
+ supported.
SSL sockets also have the following additional methods and attributes:
@@ -2249,11 +2254,12 @@ provided.
but does not provide any network IO itself. IO needs to be performed through
separate "BIO" objects which are OpenSSL's IO abstraction layer.
- An :class:`SSLObject` instance can be created using the
- :meth:`~SSLContext.wrap_bio` method. This method will create the
- :class:`SSLObject` instance and bind it to a pair of BIOs. The *incoming*
- BIO is used to pass data from Python to the SSL protocol instance, while the
- *outgoing* BIO is used to pass data the other way around.
+ This class has no public constructor. An :class:`SSLObject` instance
+ must be created using the :meth:`~SSLContext.wrap_bio` method. This
+ method will create the :class:`SSLObject` instance and bind it to a
+ pair of BIOs. The *incoming* BIO is used to pass data from Python to the
+ SSL protocol instance, while the *outgoing* BIO is used to pass data the
+ other way around.
The following methods are available:
@@ -2305,6 +2311,12 @@ provided.
:meth:`~SSLContext.wrap_socket`. An :class:`SSLObject` is always created
via an :class:`SSLContext`.
+ .. versionchanged:: 3.7
+ :class:`SSLObject` instances must to created with
+ :meth:`~SSLContext.wrap_bio`. In earlier versions, it was possible to
+ create instances directly. This was never documented or officially
+ supported.
+
An SSLObject communicates with the outside world using memory buffers. The
class :class:`MemoryBIO` provides a memory buffer that can be used for this
purpose. It wraps an OpenSSL memory BIO (Basic IO) object:
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index e25ff100a636..2d62ffa5004c 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -677,6 +677,12 @@ OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
recommend :meth:`~ssl.SSLContext.wrap_socket` instead.
(Contributed by Christian Heimes in :issue:`28124`.)
+:class:`~ssl.SSLSocket` and :class:`~ssl.SSLObject` no longer have a public
+constructor. Direct instantiation was never a documented and supported
+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`)
+
string
------
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 94ea35e358a3..75ebcc165a17 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -390,24 +390,24 @@ def wrap_socket(self, sock, server_side=False,
server_hostname=None, session=None):
# SSLSocket class handles server_hostname encoding before it calls
# ctx._wrap_socket()
- return self.sslsocket_class(
+ return self.sslsocket_class._create(
sock=sock,
server_side=server_side,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs,
server_hostname=server_hostname,
- _context=self,
- _session=session
+ context=self,
+ session=session
)
def wrap_bio(self, incoming, outgoing, server_side=False,
server_hostname=None, session=None):
# Need to encode server_hostname here because _wrap_bio() can only
# handle ASCII str.
- return self.sslobject_class(
+ return self.sslobject_class._create(
incoming, outgoing, server_side=server_side,
server_hostname=self._encode_hostname(server_hostname),
- session=session, _context=self,
+ session=session, context=self,
)
def set_npn_protocols(self, npn_protocols):
@@ -612,14 +612,23 @@ class SSLObject:
* Any form of network IO incluging methods such as ``recv`` and ``send``.
* The ``do_handshake_on_connect`` and ``suppress_ragged_eofs`` machinery.
"""
+ def __init__(self, *args, **kwargs):
+ raise TypeError(
+ f"{self.__class__.__name__} does not have a public "
+ f"constructor. Instances are returned by SSLContext.wrap_bio()."
+ )
- def __init__(self, incoming, outgoing, server_side=False,
- server_hostname=None, session=None, _context=None):
- self._sslobj = _context._wrap_bio(
+ @classmethod
+ def _create(cls, incoming, outgoing, server_side=False,
+ server_hostname=None, session=None, context=None):
+ self = cls.__new__(cls)
+ sslobj = context._wrap_bio(
incoming, outgoing, server_side=server_side,
server_hostname=server_hostname,
owner=self, session=session
)
+ self._sslobj = sslobj
+ return self
@property
def context(self):
@@ -741,72 +750,48 @@ def version(self):
class SSLSocket(socket):
"""This class implements a subtype of socket.socket that wraps
the underlying OS socket in an SSL context when necessary, and
- provides read and write methods over that channel."""
-
- def __init__(self, sock=None, keyfile=None, certfile=None,
- server_side=False, cert_reqs=CERT_NONE,
- ssl_version=PROTOCOL_TLS, ca_certs=None,
- do_handshake_on_connect=True,
- family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
- suppress_ragged_eofs=True, npn_protocols=None, ciphers=None,
- server_hostname=None,
- _context=None, _session=None):
-
- if _context:
- self._context = _context
- else:
- if server_side and not certfile:
- raise ValueError("certfile must be specified for server-side "
- "operations")
- if keyfile and not certfile:
- raise ValueError("certfile must be specified")
- if certfile and not keyfile:
- keyfile = certfile
- self._context = SSLContext(ssl_version)
- self._context.verify_mode = cert_reqs
- if ca_certs:
- self._context.load_verify_locations(ca_certs)
- if certfile:
- self._context.load_cert_chain(certfile, keyfile)
- if npn_protocols:
- self._context.set_npn_protocols(npn_protocols)
- if ciphers:
- self._context.set_ciphers(ciphers)
- self.keyfile = keyfile
- self.certfile = certfile
- self.cert_reqs = cert_reqs
- self.ssl_version = ssl_version
- self.ca_certs = ca_certs
- self.ciphers = ciphers
- # Can't use sock.type as other flags (such as SOCK_NONBLOCK) get
- # mixed in.
+ provides read and write methods over that channel. """
+
+ def __init__(self, *args, **kwargs):
+ raise TypeError(
+ f"{self.__class__.__name__} does not have a public "
+ f"constructor. Instances are returned by "
+ f"SSLContext.wrap_socket()."
+ )
+
+ @classmethod
+ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
+ suppress_ragged_eofs=True, server_hostname=None,
+ context=None, session=None):
if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM:
raise NotImplementedError("only stream sockets are supported")
if server_side:
if server_hostname:
raise ValueError("server_hostname can only be specified "
"in client mode")
- if _session is not None:
+ if session is not None:
raise ValueError("session can only be specified in "
"client mode")
- if self._context.check_hostname and not server_hostname:
+ if context.check_hostname and not server_hostname:
raise ValueError("check_hostname requires server_hostname")
- self._session = _session
+
+ kwargs = dict(
+ family=sock.family, type=sock.type, proto=sock.proto,
+ fileno=sock.fileno()
+ )
+ self = cls.__new__(cls, **kwargs)
+ super(SSLSocket, self).__init__(**kwargs)
+ self.settimeout(sock.gettimeout())
+ sock.detach()
+
+ self._context = context
+ self._session = session
+ self._closed = False
+ self._sslobj = None
self.server_side = server_side
- self.server_hostname = self._context._encode_hostname(server_hostname)
+ self.server_hostname = context._encode_hostname(server_hostname)
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
- if sock is not None:
- super().__init__(family=sock.family,
- type=sock.type,
- proto=sock.proto,
- fileno=sock.fileno())
- self.settimeout(sock.gettimeout())
- sock.detach()
- elif fileno is not None:
- super().__init__(fileno=fileno)
- else:
- super().__init__(family=family, type=type, proto=proto)
# See if we are connected
try:
@@ -818,8 +803,6 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
else:
connected = True
- self._closed = False
- self._sslobj = None
self._connected = connected
if connected:
# create the SSL object
@@ -834,10 +817,10 @@ def __init__(self, sock=None, keyfile=None, certfile=None,
# non-blocking
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
self.do_handshake()
-
except (OSError, ValueError):
self.close()
raise
+ return self
@property
def context(self):
@@ -1184,12 +1167,25 @@ def wrap_socket(sock, keyfile=None, certfile=None,
do_handshake_on_connect=True,
suppress_ragged_eofs=True,
ciphers=None):
- return SSLSocket(sock=sock, keyfile=keyfile, certfile=certfile,
- server_side=server_side, cert_reqs=cert_reqs,
- ssl_version=ssl_version, ca_certs=ca_certs,
- do_handshake_on_connect=do_handshake_on_connect,
- suppress_ragged_eofs=suppress_ragged_eofs,
- ciphers=ciphers)
+
+ if server_side and not certfile:
+ raise ValueError("certfile must be specified for server-side "
+ "operations")
+ if keyfile and not certfile:
+ raise ValueError("certfile must be specified")
+ context = SSLContext(ssl_version)
+ context.verify_mode = cert_reqs
+ if ca_certs:
+ context.load_verify_locations(ca_certs)
+ if certfile:
+ context.load_cert_chain(certfile, keyfile)
+ if ciphers:
+ context.set_ciphers(ciphers)
+ return context.wrap_socket(
+ sock=sock, server_side=server_side,
+ do_handshake_on_connect=do_handshake_on_connect,
+ suppress_ragged_eofs=suppress_ragged_eofs
+ )
# some utility functions
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index d978a9e1b0fe..ca2357e98e3e 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -263,6 +263,11 @@ def test_constants(self):
ssl.OP_NO_TLSv1_2
self.assertEqual(ssl.PROTOCOL_TLS, ssl.PROTOCOL_SSLv23)
+ def test_private_init(self):
+ with self.assertRaisesRegex(TypeError, "public constructor"):
+ with socket.socket() as s:
+ ssl.SSLSocket(s)
+
def test_str_for_enums(self):
# Make sure that the PROTOCOL_* constants have enum-like string
# reprs.
@@ -1657,6 +1662,13 @@ def test_error_types(self):
self.assertRaises(TypeError, bio.write, 1)
+class SSLObjectTests(unittest.TestCase):
+ def test_private_init(self):
+ bio = ssl.MemoryBIO()
+ with self.assertRaisesRegex(TypeError, "public constructor"):
+ ssl.SSLObject(bio, bio)
+
+
class SimpleBackgroundTests(unittest.TestCase):
"""Tests that connect to a simple server running in the background"""
@@ -2735,12 +2747,6 @@ def test_check_hostname_idn(self):
self.assertEqual(s.server_hostname, expected_hostname)
self.assertTrue(cert, "Can't get peer certificate.")
- with ssl.SSLSocket(socket.socket(),
- server_hostname=server_hostname) as s:
- s.connect((HOST, server.port))
- s.getpeercert()
- self.assertEqual(s.server_hostname, expected_hostname)
-
# incorrect hostname should raise an exception
server = ThreadedEchoServer(context=server_context, chatty=True)
with server:
@@ -3999,7 +4005,7 @@ def test_main(verbose=False):
tests = [
ContextTests, BasicSocketTests, SSLErrorTests, MemoryBIOTests,
- SimpleBackgroundTests, ThreadedTests,
+ SSLObjectTests, SimpleBackgroundTests, ThreadedTests,
]
if support.is_resource_enabled('network'):
diff --git a/Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst b/Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst
new file mode 100644
index 000000000000..9c038cf25979
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-02-25-18-22-01.bpo-32951.gHrCXq.rst
@@ -0,0 +1,3 @@
+Direct instantiation of SSLSocket and SSLObject objects is now prohibited.
+The constructors were never documented, tested, or designed as public
+constructors. Users were suppose to use ssl.wrap_socket() or SSLContext.
[View Less]
1
0
results for 4243df51fe43 on branch "default"
--------------------------------------------
test_collections leaked [7, 0, -7] memory blocks, sum=0
test_functools leaked [0, 3, 1] memory blocks, sum=4
Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogCYqzUc', '--timeout', '7200']
1
0
https://github.com/python/cpython/commit/90f05a527c7d439f1d0cba80f2eb32e60e…
commit: 90f05a527c7d439f1d0cba80f2eb32e60ee20fc3
branch: master
author: Christian Heimes <christian(a)python.org>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T09:21:34+01:00
summary:
bpo-28124: deprecate ssl.wrap_socket() (#5888)
The ssl module function ssl.wrap_socket() has been de-emphasized
and deprecated in favor of the more secure and efficient
SSLContext.wrap_socket() method.
Signed-…
[View More]off-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 5d5232eda30c..4889a7130aae 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -59,6 +59,125 @@ by SSL sockets created through the :meth:`SSLContext.wrap_socket` method.
Functions, Constants, and Exceptions
------------------------------------
+
+Socket creation
+^^^^^^^^^^^^^^^
+
+Since Python 3.2 and 2.7.9, it is recommended to use the
+:meth:`SSLContext.wrap_socket` of an :class:`SSLContext` instance to wrap
+sockets as :class:`SSLSocket` objects. The helper functions
+:func:`create_default_context` returns a new context with secure default
+settings. The old :func:`wrap_socket` function is deprecated since it is
+both inefficient and has no support for server name indication (SNI) and
+hostname matching.
+
+Client socket example with default context and IPv4/IPv6 dual stack::
+
+ import socket
+ import ssl
+
+ hostname = 'www.python.org'
+ context = ssl.create_default_context()
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+Client socket example with custom context and IPv4::
+
+ hostname = 'www.python.org'
+ # PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.load_verify_locations('path/to/cabundle.pem')
+
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+Server socket example listening on localhost IPv4::
+
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain('/path/to/certchain.pem', '/path/to/private.key')
+
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
+ sock.bind(('127.0.0.1', 8443))
+ sock.listen(5)
+ with context.wrap_socket(sock, server_side=True) as ssock:
+ conn, addr = ssock.accept()
+ ...
+
+
+Context creation
+^^^^^^^^^^^^^^^^
+
+A convenience function helps create :class:`SSLContext` objects for common
+purposes.
+
+.. function:: create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
+
+ Return a new :class:`SSLContext` object with default settings for
+ the given *purpose*. The settings are chosen by the :mod:`ssl` module,
+ and usually represent a higher security level than when calling the
+ :class:`SSLContext` constructor directly.
+
+ *cafile*, *capath*, *cadata* represent optional CA certificates to
+ trust for certificate verification, as in
+ :meth:`SSLContext.load_verify_locations`. If all three are
+ :const:`None`, this function can choose to trust the system's default
+ CA certificates instead.
+
+ The settings are: :data:`PROTOCOL_TLS`, :data:`OP_NO_SSLv2`, and
+ :data:`OP_NO_SSLv3` with high encryption cipher suites without RC4 and
+ without unauthenticated cipher suites. Passing :data:`~Purpose.SERVER_AUTH`
+ as *purpose* sets :data:`~SSLContext.verify_mode` to :data:`CERT_REQUIRED`
+ and either loads CA certificates (when at least one of *cafile*, *capath* or
+ *cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
+ default CA certificates.
+
+ .. note::
+ The protocol, options, cipher and other settings may change to more
+ restrictive values anytime without prior deprecation. The values
+ represent a fair balance between compatibility and security.
+
+ If your application needs specific settings, you should create a
+ :class:`SSLContext` and apply the settings yourself.
+
+ .. note::
+ If you find that when certain older clients or servers attempt to connect
+ with a :class:`SSLContext` created by this function that they get an error
+ stating "Protocol or cipher suite mismatch", it may be that they only
+ support SSL3.0 which this function excludes using the
+ :data:`OP_NO_SSLv3`. SSL3.0 is widely considered to be `completely broken
+ <https://en.wikipedia.org/wiki/POODLE>`_. If you still wish to continue to
+ use this function but still allow SSL 3.0 connections you can re-enable
+ them using::
+
+ ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
+ ctx.options &= ~ssl.OP_NO_SSLv3
+
+ .. versionadded:: 3.4
+
+ .. versionchanged:: 3.4.4
+
+ RC4 was dropped from the default cipher string.
+
+ .. versionchanged:: 3.6
+
+ ChaCha20/Poly1305 was added to the default cipher string.
+
+ 3DES was dropped from the default cipher string.
+
+ .. versionchanged:: 3.7
+
+ TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
+ and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
+
+
+Exceptions
+^^^^^^^^^^
+
.. exception:: SSLError
Raised to signal an error from the underlying SSL implementation
@@ -152,173 +271,6 @@ Functions, Constants, and Exceptions
The exception is now an alias for :exc:`SSLCertVerificationError`.
-Socket creation
-^^^^^^^^^^^^^^^
-
-The following function allows for standalone socket creation. Starting from
-Python 3.2, it can be more flexible to use :meth:`SSLContext.wrap_socket`
-instead.
-
-.. function:: wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)
-
- Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance
- of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps
- the underlying socket in an SSL context. ``sock`` must be a
- :data:`~socket.SOCK_STREAM` socket; other socket types are unsupported.
-
- For client-side sockets, the context construction is lazy; if the
- underlying socket isn't connected yet, the context construction will be
- performed after :meth:`connect` is called on the socket. For
- server-side sockets, if the socket has no remote peer, it is assumed
- to be a listening socket, and the server-side SSL wrapping is
- automatically performed on client connections accepted via the
- :meth:`accept` method. :func:`wrap_socket` may raise :exc:`SSLError`.
-
- The ``keyfile`` and ``certfile`` parameters specify optional files which
- contain a certificate to be used to identify the local side of the
- connection. See the discussion of :ref:`ssl-certificates` for more
- information on how the certificate is stored in the ``certfile``.
-
- The parameter ``server_side`` is a boolean which identifies whether
- server-side or client-side behavior is desired from this socket.
-
- The parameter ``cert_reqs`` specifies whether a certificate is required from
- the other side of the connection, and whether it will be validated if
- provided. It must be one of the three values :const:`CERT_NONE`
- (certificates ignored), :const:`CERT_OPTIONAL` (not required, but validated
- if provided), or :const:`CERT_REQUIRED` (required and validated). If the
- value of this parameter is not :const:`CERT_NONE`, then the ``ca_certs``
- parameter must point to a file of CA certificates.
-
- The ``ca_certs`` file contains a set of concatenated "certification
- authority" certificates, which are used to validate certificates passed from
- the other end of the connection. See the discussion of
- :ref:`ssl-certificates` for more information about how to arrange the
- certificates in this file.
-
- The parameter ``ssl_version`` specifies which version of the SSL protocol to
- use. Typically, the server chooses a particular protocol version, and the
- client must adapt to the server's choice. Most of the versions are not
- interoperable with the other versions. If not specified, the default is
- :data:`PROTOCOL_TLS`; it provides the most compatibility with other
- versions.
-
- Here's a table showing which versions in a client (down the side) can connect
- to which versions in a server (along the top):
-
- .. table::
-
- ======================== ============ ============ ============= ========= =========== ===========
- *client* / **server** **SSLv2** **SSLv3** **TLS** [3]_ **TLSv1** **TLSv1.1** **TLSv1.2**
- ------------------------ ------------ ------------ ------------- --------- ----------- -----------
- *SSLv2* yes no no [1]_ no no no
- *SSLv3* no yes no [2]_ no no no
- *TLS* (*SSLv23*) [3]_ no [1]_ no [2]_ yes yes yes yes
- *TLSv1* no no yes yes no no
- *TLSv1.1* no no yes no yes no
- *TLSv1.2* no no yes no no yes
- ======================== ============ ============ ============= ========= =========== ===========
-
- .. rubric:: Footnotes
- .. [1] :class:`SSLContext` disables SSLv2 with :data:`OP_NO_SSLv2` by default.
- .. [2] :class:`SSLContext` disables SSLv3 with :data:`OP_NO_SSLv3` by default.
- .. [3] TLS 1.3 protocol will be available with :data:`PROTOCOL_TLS` in
- OpenSSL >= 1.1.1. There is no dedicated PROTOCOL constant for just
- TLS 1.3.
-
- .. note::
-
- Which connections succeed will vary depending on the version of
- OpenSSL. For example, before OpenSSL 1.0.0, an SSLv23 client
- would always attempt SSLv2 connections.
-
- The *ciphers* parameter sets the available ciphers for this SSL object.
- It should be a string in the `OpenSSL cipher list format
- <https://wiki.openssl.org/index.php/Manual:Ciphers(1)#CIPHER_LIST_FORMAT>`_.
-
- The parameter ``do_handshake_on_connect`` specifies whether to do the SSL
- handshake automatically after doing a :meth:`socket.connect`, or whether the
- application program will call it explicitly, by invoking the
- :meth:`SSLSocket.do_handshake` method. Calling
- :meth:`SSLSocket.do_handshake` explicitly gives the program control over the
- blocking behavior of the socket I/O involved in the handshake.
-
- The parameter ``suppress_ragged_eofs`` specifies how the
- :meth:`SSLSocket.recv` method should signal unexpected EOF from the other end
- of the connection. If specified as :const:`True` (the default), it returns a
- normal EOF (an empty bytes object) in response to unexpected EOF errors
- raised from the underlying socket; if :const:`False`, it will raise the
- exceptions back to the caller.
-
- .. versionchanged:: 3.2
- New optional argument *ciphers*.
-
-Context creation
-^^^^^^^^^^^^^^^^
-
-A convenience function helps create :class:`SSLContext` objects for common
-purposes.
-
-.. function:: create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)
-
- Return a new :class:`SSLContext` object with default settings for
- the given *purpose*. The settings are chosen by the :mod:`ssl` module,
- and usually represent a higher security level than when calling the
- :class:`SSLContext` constructor directly.
-
- *cafile*, *capath*, *cadata* represent optional CA certificates to
- trust for certificate verification, as in
- :meth:`SSLContext.load_verify_locations`. If all three are
- :const:`None`, this function can choose to trust the system's default
- CA certificates instead.
-
- The settings are: :data:`PROTOCOL_TLS`, :data:`OP_NO_SSLv2`, and
- :data:`OP_NO_SSLv3` with high encryption cipher suites without RC4 and
- without unauthenticated cipher suites. Passing :data:`~Purpose.SERVER_AUTH`
- as *purpose* sets :data:`~SSLContext.verify_mode` to :data:`CERT_REQUIRED`
- and either loads CA certificates (when at least one of *cafile*, *capath* or
- *cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
- default CA certificates.
-
- .. note::
- The protocol, options, cipher and other settings may change to more
- restrictive values anytime without prior deprecation. The values
- represent a fair balance between compatibility and security.
-
- If your application needs specific settings, you should create a
- :class:`SSLContext` and apply the settings yourself.
-
- .. note::
- If you find that when certain older clients or servers attempt to connect
- with a :class:`SSLContext` created by this function that they get an error
- stating "Protocol or cipher suite mismatch", it may be that they only
- support SSL3.0 which this function excludes using the
- :data:`OP_NO_SSLv3`. SSL3.0 is widely considered to be `completely broken
- <https://en.wikipedia.org/wiki/POODLE>`_. If you still wish to continue to
- use this function but still allow SSL 3.0 connections you can re-enable
- them using::
-
- ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
- ctx.options &= ~ssl.OP_NO_SSLv3
-
- .. versionadded:: 3.4
-
- .. versionchanged:: 3.4.4
-
- RC4 was dropped from the default cipher string.
-
- .. versionchanged:: 3.6
-
- ChaCha20/Poly1305 was added to the default cipher string.
-
- 3DES was dropped from the default cipher string.
-
- .. versionchanged:: 3.7
-
- TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
- and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
-
-
Random generation
^^^^^^^^^^^^^^^^^
@@ -474,9 +426,10 @@ Certificate handling
PEM-encoded string. If ``ssl_version`` is specified, uses that version of
the SSL protocol to attempt to connect to the server. If ``ca_certs`` is
specified, it should be a file containing a list of root certificates, the
- same format as used for the same parameter in :func:`wrap_socket`. The call
- will attempt to validate the server certificate against that set of root
- certificates, and will fail if the validation attempt fails.
+ same format as used for the same parameter in
+ :meth:`SSLContext.wrap_socket`. The call will attempt to validate the
+ server certificate against that set of root certificates, and will fail
+ if the validation attempt fails.
.. versionchanged:: 3.3
This function is now IPv6-compatible.
@@ -552,6 +505,33 @@ Certificate handling
.. versionadded:: 3.4
+.. function:: wrap_socket(sock, keyfile=None, certfile=None, \
+ server_side=False, cert_reqs=CERT_NONE, ssl_version=PROTOCOL_TLS, \
+ ca_certs=None, do_handshake_on_connect=True, \
+ suppress_ragged_eofs=True, ciphers=None)
+
+ Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance
+ of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps
+ the underlying socket in an SSL context. ``sock`` must be a
+ :data:`~socket.SOCK_STREAM` socket; other socket types are unsupported.
+
+ Internally, function creates a :class:`SSLContext` with protocol
+ *ssl_version* and :attr:`SSLContext.options` set to *cert_reqs*. If
+ parameters *keyfile*, *certfile*, *ca_certs* or *ciphers* are set, then
+ the values are passed to :meth:`SSLContext.load_cert_chain`,
+ :meth:`SSLContext.load_verify_locations`, and
+ :meth:`SSLContext.set_ciphers`.
+
+ The arguments *server_side*, *do_handshake_on_connect*, and
+ *suppress_ragged_eofs* have the same meaning as
+ :meth:`SSLContext.wrap_socket`.
+
+ .. deprecated:: 3.7
+
+ Since Python 3.2 and 2.7.9, it is recommended to use the
+ :meth:`SSLContext.wrap_socket` instead of :func:`wrap_socket`. The
+ top-level function is limited and creates an insecure client socket
+ without server name indication or hostname matching.
Constants
^^^^^^^^^
@@ -1018,7 +998,7 @@ SSL Sockets
the specification of normal, OS-level sockets. See especially the
:ref:`notes on non-blocking sockets <ssl-nonblocking>`.
- Usually, :class:`SSLSocket` are not created directly, but using the
+ :class:`SSLSocket` are not created directly, but using the
:meth:`SSLContext.wrap_socket` method.
.. versionchanged:: 3.5
@@ -1257,7 +1237,7 @@ SSL sockets also have the following additional methods and attributes:
.. attribute:: SSLSocket.context
The :class:`SSLContext` object this SSL socket is tied to. If the SSL
- socket was created using the top-level :func:`wrap_socket` function
+ socket was created using the deprecated :func:`wrap_socket` function
(rather than :meth:`SSLContext.wrap_socket`), this is a custom context
object created for this SSL socket.
@@ -1310,9 +1290,36 @@ to speed up repeated connections from the same clients.
.. class:: SSLContext(protocol=PROTOCOL_TLS)
Create a new SSL context. You may pass *protocol* which must be one
- of the ``PROTOCOL_*`` constants defined in this module.
- :data:`PROTOCOL_TLS` is currently recommended for maximum
- interoperability and default value.
+ of the ``PROTOCOL_*`` constants defined in this module. The parameter
+ specifies which version of the SSL protocol to use. Typically, the
+ server chooses a particular protocol version, and the client must adapt
+ to the server's choice. Most of the versions are not interoperable
+ with the other versions. If not specified, the default is
+ :data:`PROTOCOL_TLS`; it provides the most compatibility with other
+ versions.
+
+ Here's a table showing which versions in a client (down the side) can connect
+ to which versions in a server (along the top):
+
+ .. table::
+
+ ======================== ============ ============ ============= ========= =========== ===========
+ *client* / **server** **SSLv2** **SSLv3** **TLS** [3]_ **TLSv1** **TLSv1.1** **TLSv1.2**
+ ------------------------ ------------ ------------ ------------- --------- ----------- -----------
+ *SSLv2* yes no no [1]_ no no no
+ *SSLv3* no yes no [2]_ no no no
+ *TLS* (*SSLv23*) [3]_ no [1]_ no [2]_ yes yes yes yes
+ *TLSv1* no no yes yes no no
+ *TLSv1.1* no no yes no yes no
+ *TLSv1.2* no no yes no no yes
+ ======================== ============ ============ ============= ========= =========== ===========
+
+ .. rubric:: Footnotes
+ .. [1] :class:`SSLContext` disables SSLv2 with :data:`OP_NO_SSLv2` by default.
+ .. [2] :class:`SSLContext` disables SSLv3 with :data:`OP_NO_SSLv3` by default.
+ .. [3] TLS 1.3 protocol will be available with :data:`PROTOCOL_TLS` in
+ OpenSSL >= 1.1.1. There is no dedicated PROTOCOL constant for just
+ TLS 1.3.
.. seealso::
:func:`create_default_context` lets the :mod:`ssl` module choose
@@ -1645,14 +1652,21 @@ to speed up repeated connections from the same clients.
server_hostname=None, session=None)
Wrap an existing Python socket *sock* and return an instance of
- :attr:`SSLContext.sslsocket_class` (default :class:`SSLSocket`).
- *sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket
- types are unsupported.
+ :attr:`SSLContext.sslsocket_class` (default :class:`SSLSocket`). The
+ returned SSL socket is tied to the context, its settings and certificates.
+ *sock* must be a :data:`~socket.SOCK_STREAM` socket; other
+ socket types are unsupported.
- The returned SSL socket is tied to the context, its settings and
- certificates. The parameters *server_side*, *do_handshake_on_connect*
- and *suppress_ragged_eofs* have the same meaning as in the top-level
- :func:`wrap_socket` function.
+ The parameter ``server_side`` is a boolean which identifies whether
+ server-side or client-side behavior is desired from this socket.
+
+ For client-side sockets, the context construction is lazy; if the
+ underlying socket isn't connected yet, the context construction will be
+ performed after :meth:`connect` is called on the socket. For
+ server-side sockets, if the socket has no remote peer, it is assumed
+ to be a listening socket, and the server-side SSL wrapping is
+ automatically performed on client connections accepted via the
+ :meth:`accept` method. The method may raise :exc:`SSLError`.
On client connections, the optional parameter *server_hostname* specifies
the hostname of the service which we are connecting to. This allows a
@@ -1660,6 +1674,20 @@ to speed up repeated connections from the same clients.
quite similarly to HTTP virtual hosts. Specifying *server_hostname* will
raise a :exc:`ValueError` if *server_side* is true.
+ The parameter ``do_handshake_on_connect`` specifies whether to do the SSL
+ handshake automatically after doing a :meth:`socket.connect`, or whether the
+ application program will call it explicitly, by invoking the
+ :meth:`SSLSocket.do_handshake` method. Calling
+ :meth:`SSLSocket.do_handshake` explicitly gives the program control over the
+ blocking behavior of the socket I/O involved in the handshake.
+
+ The parameter ``suppress_ragged_eofs`` specifies how the
+ :meth:`SSLSocket.recv` method should signal unexpected EOF from the other end
+ of the connection. If specified as :const:`True` (the default), it returns a
+ normal EOF (an empty bytes object) in response to unexpected EOF errors
+ raised from the underlying socket; if :const:`False`, it will raise the
+ exceptions back to the caller.
+
*session*, see :attr:`~SSLSocket.session`.
.. versionchanged:: 3.5
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 10aed52eb486..e25ff100a636 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -673,6 +673,11 @@ The ssl module has preliminary and experimental support for TLS 1.3 and
OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
:issue:`20995`, :issue:`29136`, and :issue:`30622`)
+:func:`~ssl.wrap_socket` is deprecated. Documentation has been updated to
+recommend :meth:`~ssl.SSLContext.wrap_socket` instead.
+(Contributed by Christian Heimes in :issue:`28124`.)
+
+
string
------
diff --git a/Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst b/Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst
new file mode 100644
index 000000000000..4f4ca001981d
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2018-02-25-16-33-35.bpo-28124._uzkgq.rst
@@ -0,0 +1,3 @@
+The ssl module function ssl.wrap_socket() has been de-emphasized
+and deprecated in favor of the more secure and efficient
+SSLContext.wrap_socket() method.
[View Less]
1
0

Feb. 27, 2018
https://github.com/python/cpython/commit/2614ed4c6e4b32eafb683f2378ed20e87d…
commit: 2614ed4c6e4b32eafb683f2378ed20e87d42976d
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T00:17:49-08:00
summary:
bpo-32947: OpenSSL 1.1.1-pre1 / TLS 1.3 fixes (GH-5663)
* bpo-32947: OpenSSL 1.1.1-pre1 / TLS 1.3 fixes
Misc fixes and workarounds for compatibility with OpenSSL 1.1.1-pre1 and
TLS …
[View More]1.3 support. With OpenSSL 1.1.1, Python negotiates TLS 1.3 by
default. Some test cases only apply to TLS 1.2. Other tests currently
fail because the threaded or async test servers stop after failure.
I'm going to address these issues when OpenSSL 1.1.1 reaches beta.
OpenSSL 1.1.1 has added a new option OP_ENABLE_MIDDLEBOX_COMPAT for TLS
1.3. The feature is enabled by default for maximum compatibility with
broken middle boxes. Users should be able to disable the hack and CPython's test suite needs
it to verify default options.
Signed-off-by: Christian Heimes <christian(a)python.org>
(cherry picked from commit 05d9fe32a1245b9a798e49e0c1eb91f110935b69)
Co-authored-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
M Lib/test/test_asyncio/utils.py
M Lib/test/test_ftplib.py
M Lib/test/test_poplib.py
M Lib/test/test_ssl.py
M Modules/_ssl.c
M Tools/ssl/multissltests.py
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 7371024dce4e..5d5232eda30c 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -831,6 +831,15 @@ Constants
.. versionadded:: 3.3
+.. data:: OP_ENABLE_MIDDLEBOX_COMPAT
+
+ Send dummy Change Cipher Spec (CCS) messages in TLS 1.3 handshake to make
+ a TLS 1.3 connection look more like a TLS 1.2 connection.
+
+ This option is only available with OpenSSL 1.1.1 and later.
+
+ .. versionadded:: 3.8
+
.. data:: OP_NO_COMPRESSION
Disable compression on the SSL channel. This is useful if the application
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index ecb629336499..4fad279d45e7 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -661,6 +661,9 @@ expected hostname in A-label form (``"xn--pythn-mua.org"``), rather
than the U-label form (``"pythön.org"``). (Contributed by
Nathaniel J. Smith and Christian Heimes in :issue:`28414`.)
+The ssl module has preliminary and experimental support for TLS 1.3 and
+OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
+:issue:`20995`, :issue:`29136`, and :issue:`30622`)
string
------
diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py
index 96dfe2f85b4d..711085fde5c5 100644
--- a/Lib/test/test_asyncio/utils.py
+++ b/Lib/test/test_asyncio/utils.py
@@ -74,6 +74,8 @@ def simple_server_sslcontext():
server_context.load_cert_chain(ONLYCERT, ONLYKEY)
server_context.check_hostname = False
server_context.verify_mode = ssl.CERT_NONE
+ # TODO: fix TLSv1.3 support
+ server_context.options |= ssl.OP_NO_TLSv1_3
return server_context
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
index 32aab047df85..1a8e2f91d386 100644
--- a/Lib/test/test_ftplib.py
+++ b/Lib/test/test_ftplib.py
@@ -312,6 +312,8 @@ class SSLConnection(asyncore.dispatcher):
def secure_connection(self):
context = ssl.SSLContext()
+ # TODO: fix TLSv1.3 support
+ context.options |= ssl.OP_NO_TLSv1_3
context.load_cert_chain(CERTFILE)
socket = context.wrap_socket(self.socket,
suppress_ragged_eofs=False,
@@ -908,6 +910,8 @@ def test_auth_issued_twice(self):
def test_context(self):
self.client.quit()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE,
@@ -940,6 +944,8 @@ def test_ccc(self):
def test_check_hostname(self):
self.client.quit()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(ctx.check_hostname, True)
ctx.load_verify_locations(CAFILE)
diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py
index 4d7a4394086a..fd0db798ee43 100644
--- a/Lib/test/test_poplib.py
+++ b/Lib/test/test_poplib.py
@@ -153,6 +153,8 @@ def cmd_stls(self, arg):
if self.tls_active is False:
self.push('+OK Begin TLS negotiation')
context = ssl.SSLContext()
+ # TODO: fix TLSv1.3 support
+ context.options |= ssl.OP_NO_TLSv1_3
context.load_cert_chain(CERTFILE)
tls_sock = context.wrap_socket(self.socket,
server_side=True,
@@ -356,6 +358,8 @@ def test_stls(self):
def test_stls_context(self):
expected = b'+OK Begin TLS negotiation'
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.load_verify_locations(CAFILE)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(ctx.check_hostname, True)
@@ -396,6 +400,8 @@ def test__all__(self):
def test_context(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host,
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 3b34fc0e0033..d978a9e1b0fe 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -30,7 +30,8 @@
PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
HOST = support.HOST
IS_LIBRESSL = ssl.OPENSSL_VERSION.startswith('LibreSSL')
-IS_OPENSSL_1_1 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0)
+IS_OPENSSL_1_1_0 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0)
+IS_OPENSSL_1_1_1 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 1)
PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS')
def data_file(*name):
@@ -54,6 +55,7 @@ def data_file(*name):
BYTES_CAPATH = os.fsencode(CAPATH)
CAFILE_NEURONIO = data_file("capath", "4e1295a3.0")
CAFILE_CACERT = data_file("capath", "5ed36f99.0")
+WRONG_CERT = data_file("wrongcert.pem")
CERTFILE_INFO = {
'issuer': ((('countryName', 'XY'),),
@@ -124,6 +126,7 @@ def data_file(*name):
OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0)
OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
+OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0)
def handle_error(prefix):
@@ -232,6 +235,7 @@ def testing_context(server_cert=SIGNED_CERTFILE):
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain(server_cert)
+ client_context.load_verify_locations(SIGNING_CA)
return client_context, server_context, hostname
@@ -1016,7 +1020,8 @@ def test_options(self):
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
# SSLContext also enables these by default
default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
- OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
+ OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE |
+ OP_ENABLE_MIDDLEBOX_COMPAT)
self.assertEqual(default, ctx.options)
ctx.options |= ssl.OP_NO_TLSv1
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
@@ -1778,6 +1783,8 @@ def test_connect_cadata(self):
der = ssl.PEM_cert_to_DER_cert(pem)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
ctx.verify_mode = ssl.CERT_REQUIRED
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.load_verify_locations(cadata=pem)
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
s.connect(self.server_addr)
@@ -1787,6 +1794,8 @@ def test_connect_cadata(self):
# same with DER
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
ctx.verify_mode = ssl.CERT_REQUIRED
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.load_verify_locations(cadata=der)
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
s.connect(self.server_addr)
@@ -2629,7 +2638,10 @@ def test_check_hostname(self):
def test_ecc_cert(self):
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_verify_locations(SIGNING_CA)
- client_context.set_ciphers('ECDHE:ECDSA:!NULL:!aRSA')
+ client_context.set_ciphers(
+ 'TLS13-AES-128-GCM-SHA256:TLS13-CHACHA20-POLY1305-SHA256:'
+ 'ECDHE:ECDSA:!NULL:!aRSA'
+ )
hostname = SIGNED_CERTFILE_ECC_HOSTNAME
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
@@ -2650,6 +2662,9 @@ def test_ecc_cert(self):
def test_dual_rsa_ecc(self):
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_verify_locations(SIGNING_CA)
+ # TODO: fix TLSv1.3 once SSLContext can restrict signature
+ # algorithms.
+ client_context.options |= ssl.OP_NO_TLSv1_3
# only ECDSA certs
client_context.set_ciphers('ECDHE:ECDSA:!NULL:!aRSA')
hostname = SIGNED_CERTFILE_ECC_HOSTNAME
@@ -2676,6 +2691,8 @@ def test_check_hostname_idn(self):
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain(IDNSANSFILE)
+ # TODO: fix TLSv1.3 support
+ server_context.options |= ssl.OP_NO_TLSv1_3
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
@@ -2738,15 +2755,22 @@ def test_wrong_cert(self):
Launch a server with CERT_REQUIRED, and check that trying to
connect to it with a wrong client certificate fails.
"""
- certfile = os.path.join(os.path.dirname(__file__) or os.curdir,
- "wrongcert.pem")
- server = ThreadedEchoServer(CERTFILE,
- certreqs=ssl.CERT_REQUIRED,
- cacerts=CERTFILE, chatty=False,
- connectionchatty=False)
+ client_context, server_context, hostname = testing_context()
+ # load client cert
+ client_context.load_cert_chain(WRONG_CERT)
+ # require TLS client authentication
+ server_context.verify_mode = ssl.CERT_REQUIRED
+ # TODO: fix TLSv1.3 support
+ # With TLS 1.3, test fails with exception in server thread
+ server_context.options |= ssl.OP_NO_TLSv1_3
+
+ server = ThreadedEchoServer(
+ context=server_context, chatty=True, connectionchatty=True,
+ )
+
with server, \
- socket.socket() as sock, \
- test_wrap_socket(sock, certfile=certfile) as s:
+ client_context.wrap_socket(socket.socket(),
+ server_hostname=hostname) as s:
try:
# Expect either an SSL error about the server rejecting
# the connection, or a low-level connection reset (which
@@ -3400,7 +3424,9 @@ def test_version_basic(self):
self.assertIs(s.version(), None)
self.assertIs(s._sslobj, None)
s.connect((HOST, server.port))
- if ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
+ if ssl.OPENSSL_VERSION_INFO >= (1, 1, 1):
+ self.assertEqual(s.version(), 'TLSv1.3')
+ elif ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
self.assertEqual(s.version(), 'TLSv1.2')
else: # 0.9.8 to 1.0.1
self.assertIn(s.version(), ('TLSv1', 'TLSv1.2'))
@@ -3412,18 +3438,18 @@ def test_version_basic(self):
def test_tls1_3(self):
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.load_cert_chain(CERTFILE)
- # disable all but TLS 1.3
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((HOST, server.port))
- self.assertIn(s.cipher()[0], [
+ self.assertIn(s.cipher()[0], {
'TLS13-AES-256-GCM-SHA384',
'TLS13-CHACHA20-POLY1305-SHA256',
'TLS13-AES-128-GCM-SHA256',
- ])
+ })
+ self.assertEqual(s.version(), 'TLSv1.3')
@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
def test_default_ecdh_curve(self):
@@ -3452,58 +3478,54 @@ def test_tls_unique_channel_binding(self):
if support.verbose:
sys.stdout.write("\n")
- server = ThreadedEchoServer(CERTFILE,
- certreqs=ssl.CERT_NONE,
- ssl_version=ssl.PROTOCOL_TLS_SERVER,
- cacerts=CERTFILE,
+ client_context, server_context, hostname = testing_context()
+ # TODO: fix TLSv1.3 support
+ client_context.options |= ssl.OP_NO_TLSv1_3
+
+ server = ThreadedEchoServer(context=server_context,
chatty=True,
connectionchatty=False)
+
with server:
- s = test_wrap_socket(socket.socket(),
- server_side=False,
- certfile=CERTFILE,
- ca_certs=CERTFILE,
- cert_reqs=ssl.CERT_NONE,
- ssl_version=ssl.PROTOCOL_TLS_CLIENT)
- s.connect((HOST, server.port))
- # get the data
- cb_data = s.get_channel_binding("tls-unique")
- if support.verbose:
- sys.stdout.write(" got channel binding data: {0!r}\n"
- .format(cb_data))
-
- # check if it is sane
- self.assertIsNotNone(cb_data)
- self.assertEqual(len(cb_data), 12) # True for TLSv1
-
- # and compare with the peers version
- s.write(b"CB tls-unique\n")
- peer_data_repr = s.read().strip()
- self.assertEqual(peer_data_repr,
- repr(cb_data).encode("us-ascii"))
- s.close()
+ with client_context.wrap_socket(
+ socket.socket(),
+ server_hostname=hostname) as s:
+ s.connect((HOST, server.port))
+ # get the data
+ cb_data = s.get_channel_binding("tls-unique")
+ if support.verbose:
+ sys.stdout.write(
+ " got channel binding data: {0!r}\n".format(cb_data))
+
+ # check if it is sane
+ self.assertIsNotNone(cb_data)
+ self.assertEqual(len(cb_data), 12) # True for TLSv1
+
+ # and compare with the peers version
+ s.write(b"CB tls-unique\n")
+ peer_data_repr = s.read().strip()
+ self.assertEqual(peer_data_repr,
+ repr(cb_data).encode("us-ascii"))
# now, again
- s = test_wrap_socket(socket.socket(),
- server_side=False,
- certfile=CERTFILE,
- ca_certs=CERTFILE,
- cert_reqs=ssl.CERT_NONE,
- ssl_version=ssl.PROTOCOL_TLS_CLIENT)
- s.connect((HOST, server.port))
- new_cb_data = s.get_channel_binding("tls-unique")
- if support.verbose:
- sys.stdout.write(" got another channel binding data: {0!r}\n"
- .format(new_cb_data))
- # is it really unique
- self.assertNotEqual(cb_data, new_cb_data)
- self.assertIsNotNone(cb_data)
- self.assertEqual(len(cb_data), 12) # True for TLSv1
- s.write(b"CB tls-unique\n")
- peer_data_repr = s.read().strip()
- self.assertEqual(peer_data_repr,
- repr(new_cb_data).encode("us-ascii"))
- s.close()
+ with client_context.wrap_socket(
+ socket.socket(),
+ server_hostname=hostname) as s:
+ s.connect((HOST, server.port))
+ new_cb_data = s.get_channel_binding("tls-unique")
+ if support.verbose:
+ sys.stdout.write(
+ "got another channel binding data: {0!r}\n".format(
+ new_cb_data)
+ )
+ # is it really unique
+ self.assertNotEqual(cb_data, new_cb_data)
+ self.assertIsNotNone(cb_data)
+ self.assertEqual(len(cb_data), 12) # True for TLSv1
+ s.write(b"CB tls-unique\n")
+ peer_data_repr = s.read().strip()
+ self.assertEqual(peer_data_repr,
+ repr(new_cb_data).encode("us-ascii"))
def test_compression(self):
client_context, server_context, hostname = testing_context()
@@ -3528,8 +3550,11 @@ def test_compression_disabled(self):
def test_dh_params(self):
# Check we can get a connection with ephemeral Diffie-Hellman
client_context, server_context, hostname = testing_context()
+ # test scenario needs TLS <= 1.2
+ client_context.options |= ssl.OP_NO_TLSv1_3
server_context.load_dh_params(DHFILE)
server_context.set_ciphers("kEDH")
+ server_context.options |= ssl.OP_NO_TLSv1_3
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)
@@ -3539,9 +3564,11 @@ def test_dh_params(self):
self.fail("Non-DH cipher: " + cipher[0])
@unittest.skipUnless(HAVE_SECP_CURVES, "needs secp384r1 curve support")
+ @unittest.skipIf(IS_OPENSSL_1_1_1, "TODO: Test doesn't work on 1.1.1")
def test_ecdh_curve(self):
# server secp384r1, client auto
client_context, server_context, hostname = testing_context()
+
server_context.set_ecdh_curve("secp384r1")
server_context.set_ciphers("ECDHE:!eNULL:!aNULL")
server_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
@@ -3572,7 +3599,7 @@ def test_ecdh_curve(self):
pass
else:
# OpenSSL 1.0.2 does not fail although it should.
- if IS_OPENSSL_1_1:
+ if IS_OPENSSL_1_1_0:
self.fail("mismatch curve did not fail")
def test_selected_alpn_protocol(self):
@@ -3616,7 +3643,7 @@ def test_alpn_protocols(self):
except ssl.SSLError as e:
stats = e
- if (expected is None and IS_OPENSSL_1_1
+ if (expected is None and IS_OPENSSL_1_1_0
and ssl.OPENSSL_VERSION_INFO < (1, 1, 0, 6)):
# OpenSSL 1.1.0 to 1.1.0e raises handshake error
self.assertIsInstance(stats, ssl.SSLError)
@@ -3823,6 +3850,8 @@ def test_sendfile(self):
def test_session(self):
client_context, server_context, hostname = testing_context()
+ # TODO: sessions aren't compatible with TLSv1.3 yet
+ client_context.options |= ssl.OP_NO_TLSv1_3
# first connection without session
stats = server_params_test(client_context, server_context,
@@ -3881,7 +3910,7 @@ def test_session_handling(self):
client_context, server_context, hostname = testing_context()
client_context2, _, _ = testing_context()
- # TODO: session reuse does not work with TLS 1.3
+ # TODO: session reuse does not work with TLSv1.3
client_context.options |= ssl.OP_NO_TLSv1_3
client_context2.options |= ssl.OP_NO_TLSv1_3
diff --git a/Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst b/Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst
new file mode 100644
index 000000000000..28de360c3671
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst
@@ -0,0 +1,2 @@
+Add OP_ENABLE_MIDDLEBOX_COMPAT and test workaround for TLSv1.3 for future
+compatibility with OpenSSL 1.1.1.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 52695fe39c71..f50823e6947a 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -5681,6 +5681,10 @@ PyInit__ssl(void)
PyModule_AddIntConstant(m, "OP_NO_COMPRESSION",
SSL_OP_NO_COMPRESSION);
#endif
+#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+ PyModule_AddIntConstant(m, "OP_ENABLE_MIDDLEBOX_COMPAT",
+ SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
+#endif
#ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
PyModule_AddIntConstant(m, "HOSTFLAG_ALWAYS_CHECK_SUBJECT",
diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py
index 75874cfca4d0..70913c7203b3 100755
--- a/Tools/ssl/multissltests.py
+++ b/Tools/ssl/multissltests.py
@@ -41,24 +41,20 @@
log = logging.getLogger("multissl")
OPENSSL_OLD_VERSIONS = [
- # "0.9.8zh",
- # "1.0.1u",
+ "1.0.2",
]
OPENSSL_RECENT_VERSIONS = [
- "1.0.2",
- "1.0.2m",
- "1.1.0g",
+ "1.0.2n",
+ "1.1.0g",
+ "1.1.1-pre1",
]
LIBRESSL_OLD_VERSIONS = [
- # "2.3.10",
- # "2.4.5",
]
LIBRESSL_RECENT_VERSIONS = [
- "2.5.3",
- "2.5.5",
+ # "2.6.5",
]
# store files in ../multissl
[View Less]
1
0

Feb. 27, 2018
https://github.com/python/cpython/commit/05d9fe32a1245b9a798e49e0c1eb91f110…
commit: 05d9fe32a1245b9a798e49e0c1eb91f110935b69
branch: master
author: Christian Heimes <christian(a)python.org>
committer: GitHub <noreply(a)github.com>
date: 2018-02-27T08:55:39+01:00
summary:
bpo-32947: OpenSSL 1.1.1-pre1 / TLS 1.3 fixes (#5663)
* bpo-32947: OpenSSL 1.1.1-pre1 / TLS 1.3 fixes
Misc fixes and workarounds for compatibility with OpenSSL 1.1.1-pre1 and
TLS 1.3 support. With OpenSSL 1.1.1, …
[View More]Python negotiates TLS 1.3 by
default. Some test cases only apply to TLS 1.2. Other tests currently
fail because the threaded or async test servers stop after failure.
I'm going to address these issues when OpenSSL 1.1.1 reaches beta.
OpenSSL 1.1.1 has added a new option OP_ENABLE_MIDDLEBOX_COMPAT for TLS
1.3. The feature is enabled by default for maximum compatibility with
broken middle boxes. Users should be able to disable the hack and CPython's test suite needs
it to verify default options.
Signed-off-by: Christian Heimes <christian(a)python.org>
files:
A Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.7.rst
M Lib/test/test_asyncio/utils.py
M Lib/test/test_ftplib.py
M Lib/test/test_poplib.py
M Lib/test/test_ssl.py
M Modules/_ssl.c
M Tools/ssl/multissltests.py
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 7371024dce4e..5d5232eda30c 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -831,6 +831,15 @@ Constants
.. versionadded:: 3.3
+.. data:: OP_ENABLE_MIDDLEBOX_COMPAT
+
+ Send dummy Change Cipher Spec (CCS) messages in TLS 1.3 handshake to make
+ a TLS 1.3 connection look more like a TLS 1.2 connection.
+
+ This option is only available with OpenSSL 1.1.1 and later.
+
+ .. versionadded:: 3.8
+
.. data:: OP_NO_COMPRESSION
Disable compression on the SSL channel. This is useful if the application
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index c924f6ddd471..10aed52eb486 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -669,6 +669,9 @@ expected hostname in A-label form (``"xn--pythn-mua.org"``), rather
than the U-label form (``"pythön.org"``). (Contributed by
Nathaniel J. Smith and Christian Heimes in :issue:`28414`.)
+The ssl module has preliminary and experimental support for TLS 1.3 and
+OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
+:issue:`20995`, :issue:`29136`, and :issue:`30622`)
string
------
diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py
index 96dfe2f85b4d..711085fde5c5 100644
--- a/Lib/test/test_asyncio/utils.py
+++ b/Lib/test/test_asyncio/utils.py
@@ -74,6 +74,8 @@ def simple_server_sslcontext():
server_context.load_cert_chain(ONLYCERT, ONLYKEY)
server_context.check_hostname = False
server_context.verify_mode = ssl.CERT_NONE
+ # TODO: fix TLSv1.3 support
+ server_context.options |= ssl.OP_NO_TLSv1_3
return server_context
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
index 32aab047df85..1a8e2f91d386 100644
--- a/Lib/test/test_ftplib.py
+++ b/Lib/test/test_ftplib.py
@@ -312,6 +312,8 @@ class SSLConnection(asyncore.dispatcher):
def secure_connection(self):
context = ssl.SSLContext()
+ # TODO: fix TLSv1.3 support
+ context.options |= ssl.OP_NO_TLSv1_3
context.load_cert_chain(CERTFILE)
socket = context.wrap_socket(self.socket,
suppress_ragged_eofs=False,
@@ -908,6 +910,8 @@ def test_auth_issued_twice(self):
def test_context(self):
self.client.quit()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE,
@@ -940,6 +944,8 @@ def test_ccc(self):
def test_check_hostname(self):
self.client.quit()
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(ctx.check_hostname, True)
ctx.load_verify_locations(CAFILE)
diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py
index 4d7a4394086a..fd0db798ee43 100644
--- a/Lib/test/test_poplib.py
+++ b/Lib/test/test_poplib.py
@@ -153,6 +153,8 @@ def cmd_stls(self, arg):
if self.tls_active is False:
self.push('+OK Begin TLS negotiation')
context = ssl.SSLContext()
+ # TODO: fix TLSv1.3 support
+ context.options |= ssl.OP_NO_TLSv1_3
context.load_cert_chain(CERTFILE)
tls_sock = context.wrap_socket(self.socket,
server_side=True,
@@ -356,6 +358,8 @@ def test_stls(self):
def test_stls_context(self):
expected = b'+OK Begin TLS negotiation'
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.load_verify_locations(CAFILE)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(ctx.check_hostname, True)
@@ -396,6 +400,8 @@ def test__all__(self):
def test_context(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host,
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 3b34fc0e0033..d978a9e1b0fe 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -30,7 +30,8 @@
PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
HOST = support.HOST
IS_LIBRESSL = ssl.OPENSSL_VERSION.startswith('LibreSSL')
-IS_OPENSSL_1_1 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0)
+IS_OPENSSL_1_1_0 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0)
+IS_OPENSSL_1_1_1 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 1)
PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS')
def data_file(*name):
@@ -54,6 +55,7 @@ def data_file(*name):
BYTES_CAPATH = os.fsencode(CAPATH)
CAFILE_NEURONIO = data_file("capath", "4e1295a3.0")
CAFILE_CACERT = data_file("capath", "5ed36f99.0")
+WRONG_CERT = data_file("wrongcert.pem")
CERTFILE_INFO = {
'issuer': ((('countryName', 'XY'),),
@@ -124,6 +126,7 @@ def data_file(*name):
OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0)
OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
+OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0)
def handle_error(prefix):
@@ -232,6 +235,7 @@ def testing_context(server_cert=SIGNED_CERTFILE):
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain(server_cert)
+ client_context.load_verify_locations(SIGNING_CA)
return client_context, server_context, hostname
@@ -1016,7 +1020,8 @@ def test_options(self):
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
# SSLContext also enables these by default
default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
- OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
+ OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE |
+ OP_ENABLE_MIDDLEBOX_COMPAT)
self.assertEqual(default, ctx.options)
ctx.options |= ssl.OP_NO_TLSv1
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
@@ -1778,6 +1783,8 @@ def test_connect_cadata(self):
der = ssl.PEM_cert_to_DER_cert(pem)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
ctx.verify_mode = ssl.CERT_REQUIRED
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.load_verify_locations(cadata=pem)
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
s.connect(self.server_addr)
@@ -1787,6 +1794,8 @@ def test_connect_cadata(self):
# same with DER
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
ctx.verify_mode = ssl.CERT_REQUIRED
+ # TODO: fix TLSv1.3 support
+ ctx.options |= ssl.OP_NO_TLSv1_3
ctx.load_verify_locations(cadata=der)
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
s.connect(self.server_addr)
@@ -2629,7 +2638,10 @@ def test_check_hostname(self):
def test_ecc_cert(self):
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_verify_locations(SIGNING_CA)
- client_context.set_ciphers('ECDHE:ECDSA:!NULL:!aRSA')
+ client_context.set_ciphers(
+ 'TLS13-AES-128-GCM-SHA256:TLS13-CHACHA20-POLY1305-SHA256:'
+ 'ECDHE:ECDSA:!NULL:!aRSA'
+ )
hostname = SIGNED_CERTFILE_ECC_HOSTNAME
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
@@ -2650,6 +2662,9 @@ def test_ecc_cert(self):
def test_dual_rsa_ecc(self):
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_verify_locations(SIGNING_CA)
+ # TODO: fix TLSv1.3 once SSLContext can restrict signature
+ # algorithms.
+ client_context.options |= ssl.OP_NO_TLSv1_3
# only ECDSA certs
client_context.set_ciphers('ECDHE:ECDSA:!NULL:!aRSA')
hostname = SIGNED_CERTFILE_ECC_HOSTNAME
@@ -2676,6 +2691,8 @@ def test_check_hostname_idn(self):
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain(IDNSANSFILE)
+ # TODO: fix TLSv1.3 support
+ server_context.options |= ssl.OP_NO_TLSv1_3
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
@@ -2738,15 +2755,22 @@ def test_wrong_cert(self):
Launch a server with CERT_REQUIRED, and check that trying to
connect to it with a wrong client certificate fails.
"""
- certfile = os.path.join(os.path.dirname(__file__) or os.curdir,
- "wrongcert.pem")
- server = ThreadedEchoServer(CERTFILE,
- certreqs=ssl.CERT_REQUIRED,
- cacerts=CERTFILE, chatty=False,
- connectionchatty=False)
+ client_context, server_context, hostname = testing_context()
+ # load client cert
+ client_context.load_cert_chain(WRONG_CERT)
+ # require TLS client authentication
+ server_context.verify_mode = ssl.CERT_REQUIRED
+ # TODO: fix TLSv1.3 support
+ # With TLS 1.3, test fails with exception in server thread
+ server_context.options |= ssl.OP_NO_TLSv1_3
+
+ server = ThreadedEchoServer(
+ context=server_context, chatty=True, connectionchatty=True,
+ )
+
with server, \
- socket.socket() as sock, \
- test_wrap_socket(sock, certfile=certfile) as s:
+ client_context.wrap_socket(socket.socket(),
+ server_hostname=hostname) as s:
try:
# Expect either an SSL error about the server rejecting
# the connection, or a low-level connection reset (which
@@ -3400,7 +3424,9 @@ def test_version_basic(self):
self.assertIs(s.version(), None)
self.assertIs(s._sslobj, None)
s.connect((HOST, server.port))
- if ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
+ if ssl.OPENSSL_VERSION_INFO >= (1, 1, 1):
+ self.assertEqual(s.version(), 'TLSv1.3')
+ elif ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
self.assertEqual(s.version(), 'TLSv1.2')
else: # 0.9.8 to 1.0.1
self.assertIn(s.version(), ('TLSv1', 'TLSv1.2'))
@@ -3412,18 +3438,18 @@ def test_version_basic(self):
def test_tls1_3(self):
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.load_cert_chain(CERTFILE)
- # disable all but TLS 1.3
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((HOST, server.port))
- self.assertIn(s.cipher()[0], [
+ self.assertIn(s.cipher()[0], {
'TLS13-AES-256-GCM-SHA384',
'TLS13-CHACHA20-POLY1305-SHA256',
'TLS13-AES-128-GCM-SHA256',
- ])
+ })
+ self.assertEqual(s.version(), 'TLSv1.3')
@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
def test_default_ecdh_curve(self):
@@ -3452,58 +3478,54 @@ def test_tls_unique_channel_binding(self):
if support.verbose:
sys.stdout.write("\n")
- server = ThreadedEchoServer(CERTFILE,
- certreqs=ssl.CERT_NONE,
- ssl_version=ssl.PROTOCOL_TLS_SERVER,
- cacerts=CERTFILE,
+ client_context, server_context, hostname = testing_context()
+ # TODO: fix TLSv1.3 support
+ client_context.options |= ssl.OP_NO_TLSv1_3
+
+ server = ThreadedEchoServer(context=server_context,
chatty=True,
connectionchatty=False)
+
with server:
- s = test_wrap_socket(socket.socket(),
- server_side=False,
- certfile=CERTFILE,
- ca_certs=CERTFILE,
- cert_reqs=ssl.CERT_NONE,
- ssl_version=ssl.PROTOCOL_TLS_CLIENT)
- s.connect((HOST, server.port))
- # get the data
- cb_data = s.get_channel_binding("tls-unique")
- if support.verbose:
- sys.stdout.write(" got channel binding data: {0!r}\n"
- .format(cb_data))
-
- # check if it is sane
- self.assertIsNotNone(cb_data)
- self.assertEqual(len(cb_data), 12) # True for TLSv1
-
- # and compare with the peers version
- s.write(b"CB tls-unique\n")
- peer_data_repr = s.read().strip()
- self.assertEqual(peer_data_repr,
- repr(cb_data).encode("us-ascii"))
- s.close()
+ with client_context.wrap_socket(
+ socket.socket(),
+ server_hostname=hostname) as s:
+ s.connect((HOST, server.port))
+ # get the data
+ cb_data = s.get_channel_binding("tls-unique")
+ if support.verbose:
+ sys.stdout.write(
+ " got channel binding data: {0!r}\n".format(cb_data))
+
+ # check if it is sane
+ self.assertIsNotNone(cb_data)
+ self.assertEqual(len(cb_data), 12) # True for TLSv1
+
+ # and compare with the peers version
+ s.write(b"CB tls-unique\n")
+ peer_data_repr = s.read().strip()
+ self.assertEqual(peer_data_repr,
+ repr(cb_data).encode("us-ascii"))
# now, again
- s = test_wrap_socket(socket.socket(),
- server_side=False,
- certfile=CERTFILE,
- ca_certs=CERTFILE,
- cert_reqs=ssl.CERT_NONE,
- ssl_version=ssl.PROTOCOL_TLS_CLIENT)
- s.connect((HOST, server.port))
- new_cb_data = s.get_channel_binding("tls-unique")
- if support.verbose:
- sys.stdout.write(" got another channel binding data: {0!r}\n"
- .format(new_cb_data))
- # is it really unique
- self.assertNotEqual(cb_data, new_cb_data)
- self.assertIsNotNone(cb_data)
- self.assertEqual(len(cb_data), 12) # True for TLSv1
- s.write(b"CB tls-unique\n")
- peer_data_repr = s.read().strip()
- self.assertEqual(peer_data_repr,
- repr(new_cb_data).encode("us-ascii"))
- s.close()
+ with client_context.wrap_socket(
+ socket.socket(),
+ server_hostname=hostname) as s:
+ s.connect((HOST, server.port))
+ new_cb_data = s.get_channel_binding("tls-unique")
+ if support.verbose:
+ sys.stdout.write(
+ "got another channel binding data: {0!r}\n".format(
+ new_cb_data)
+ )
+ # is it really unique
+ self.assertNotEqual(cb_data, new_cb_data)
+ self.assertIsNotNone(cb_data)
+ self.assertEqual(len(cb_data), 12) # True for TLSv1
+ s.write(b"CB tls-unique\n")
+ peer_data_repr = s.read().strip()
+ self.assertEqual(peer_data_repr,
+ repr(new_cb_data).encode("us-ascii"))
def test_compression(self):
client_context, server_context, hostname = testing_context()
@@ -3528,8 +3550,11 @@ def test_compression_disabled(self):
def test_dh_params(self):
# Check we can get a connection with ephemeral Diffie-Hellman
client_context, server_context, hostname = testing_context()
+ # test scenario needs TLS <= 1.2
+ client_context.options |= ssl.OP_NO_TLSv1_3
server_context.load_dh_params(DHFILE)
server_context.set_ciphers("kEDH")
+ server_context.options |= ssl.OP_NO_TLSv1_3
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)
@@ -3539,9 +3564,11 @@ def test_dh_params(self):
self.fail("Non-DH cipher: " + cipher[0])
@unittest.skipUnless(HAVE_SECP_CURVES, "needs secp384r1 curve support")
+ @unittest.skipIf(IS_OPENSSL_1_1_1, "TODO: Test doesn't work on 1.1.1")
def test_ecdh_curve(self):
# server secp384r1, client auto
client_context, server_context, hostname = testing_context()
+
server_context.set_ecdh_curve("secp384r1")
server_context.set_ciphers("ECDHE:!eNULL:!aNULL")
server_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
@@ -3572,7 +3599,7 @@ def test_ecdh_curve(self):
pass
else:
# OpenSSL 1.0.2 does not fail although it should.
- if IS_OPENSSL_1_1:
+ if IS_OPENSSL_1_1_0:
self.fail("mismatch curve did not fail")
def test_selected_alpn_protocol(self):
@@ -3616,7 +3643,7 @@ def test_alpn_protocols(self):
except ssl.SSLError as e:
stats = e
- if (expected is None and IS_OPENSSL_1_1
+ if (expected is None and IS_OPENSSL_1_1_0
and ssl.OPENSSL_VERSION_INFO < (1, 1, 0, 6)):
# OpenSSL 1.1.0 to 1.1.0e raises handshake error
self.assertIsInstance(stats, ssl.SSLError)
@@ -3823,6 +3850,8 @@ def test_sendfile(self):
def test_session(self):
client_context, server_context, hostname = testing_context()
+ # TODO: sessions aren't compatible with TLSv1.3 yet
+ client_context.options |= ssl.OP_NO_TLSv1_3
# first connection without session
stats = server_params_test(client_context, server_context,
@@ -3881,7 +3910,7 @@ def test_session_handling(self):
client_context, server_context, hostname = testing_context()
client_context2, _, _ = testing_context()
- # TODO: session reuse does not work with TLS 1.3
+ # TODO: session reuse does not work with TLSv1.3
client_context.options |= ssl.OP_NO_TLSv1_3
client_context2.options |= ssl.OP_NO_TLSv1_3
diff --git a/Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst b/Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst
new file mode 100644
index 000000000000..28de360c3671
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-02-25-13-06-21.bpo-32947.mqStVW.rst
@@ -0,0 +1,2 @@
+Add OP_ENABLE_MIDDLEBOX_COMPAT and test workaround for TLSv1.3 for future
+compatibility with OpenSSL 1.1.1.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 52695fe39c71..f50823e6947a 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -5681,6 +5681,10 @@ PyInit__ssl(void)
PyModule_AddIntConstant(m, "OP_NO_COMPRESSION",
SSL_OP_NO_COMPRESSION);
#endif
+#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+ PyModule_AddIntConstant(m, "OP_ENABLE_MIDDLEBOX_COMPAT",
+ SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
+#endif
#ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
PyModule_AddIntConstant(m, "HOSTFLAG_ALWAYS_CHECK_SUBJECT",
diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py
index 75874cfca4d0..70913c7203b3 100755
--- a/Tools/ssl/multissltests.py
+++ b/Tools/ssl/multissltests.py
@@ -41,24 +41,20 @@
log = logging.getLogger("multissl")
OPENSSL_OLD_VERSIONS = [
- # "0.9.8zh",
- # "1.0.1u",
+ "1.0.2",
]
OPENSSL_RECENT_VERSIONS = [
- "1.0.2",
- "1.0.2m",
- "1.1.0g",
+ "1.0.2n",
+ "1.1.0g",
+ "1.1.1-pre1",
]
LIBRESSL_OLD_VERSIONS = [
- # "2.3.10",
- # "2.4.5",
]
LIBRESSL_RECENT_VERSIONS = [
- "2.5.3",
- "2.5.5",
+ # "2.6.5",
]
# store files in ../multissl
[View Less]
1
0

bpo-32960: For dataclasses, disallow inheriting frozen from non-frozen classes and vice-versa, (GH-5919) (GH-5920)
by Eric V. Smith Feb. 27, 2018
by Eric V. Smith Feb. 27, 2018
Feb. 27, 2018
https://github.com/python/cpython/commit/a93e3dc236279692eaf50b91d358da5983…
commit: a93e3dc236279692eaf50b91d358da5983983b14
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: Eric V. Smith <ericvsmith(a)users.noreply.github.com>
date: 2018-02-26T20:59:55-05:00
summary:
bpo-32960: For dataclasses, disallow inheriting frozen from non-frozen classes and vice-versa, (GH-5919) (GH-5920)
This restriction will be relaxed at a future …
[View More]date.
(cherry picked from commit 2fa6b9eae07e2385e2acbf2e40093a21fb3a10c4)
Co-authored-by: Eric V. Smith <ericvsmith(a)users.noreply.github.com>
files:
A Misc/NEWS.d/next/Library/2018-02-26-20-04-40.bpo-32960.48r0Ml.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses.py
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index db92b10960d5..54478fec93df 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -623,14 +623,21 @@ def _process_class(cls, repr, eq, order, unsafe_hash, init, frozen):
else:
setattr(cls, f.name, f.default)
+ # We're inheriting from a frozen dataclass, but we're not frozen.
+ if cls.__setattr__ is _frozen_setattr and not frozen:
+ raise TypeError('cannot inherit non-frozen dataclass from a '
+ 'frozen one')
+
+ # We're inheriting from a non-frozen dataclass, but we're frozen.
+ if (hasattr(cls, _MARKER) and cls.__setattr__ is not _frozen_setattr
+ and frozen):
+ raise TypeError('cannot inherit frozen dataclass from a '
+ 'non-frozen one')
+
# Remember all of the fields on our class (including bases). This
# marks this class as being a dataclass.
setattr(cls, _MARKER, fields)
- # We also need to check if a parent class is frozen: frozen has to
- # be inherited down.
- is_frozen = frozen or cls.__setattr__ is _frozen_setattr
-
# Was this class defined with an explicit __hash__? Note that if
# __eq__ is defined in this class, then python will automatically
# set __hash__ to None. This is a heuristic, as it's possible
@@ -654,7 +661,7 @@ def _process_class(cls, repr, eq, order, unsafe_hash, init, frozen):
if f._field_type in (_FIELD, _FIELD_INITVAR)]
_set_new_attribute(cls, '__init__',
_init_fn(flds,
- is_frozen,
+ frozen,
has_post_init,
# The name to use for the "self" param
# in __init__. Use "self" if possible.
@@ -696,7 +703,7 @@ def _process_class(cls, repr, eq, order, unsafe_hash, init, frozen):
f'in class {cls.__name__}. Consider using '
'functools.total_ordering')
- if is_frozen:
+ if frozen:
for name, fn in [('__setattr__', _frozen_setattr),
('__delattr__', _frozen_delattr)]:
if _set_new_attribute(cls, name, fn):
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index 582cb3459f5d..46d485c0157b 100755
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -637,29 +637,6 @@ class C:
y: int
self.assertNotEqual(Point(1, 3), C(1, 3))
- def test_frozen(self):
- @dataclass(frozen=True)
- class C:
- i: int
-
- c = C(10)
- self.assertEqual(c.i, 10)
- with self.assertRaises(FrozenInstanceError):
- c.i = 5
- self.assertEqual(c.i, 10)
-
- # Check that a derived class is still frozen, even if not
- # marked so.
- @dataclass
- class D(C):
- pass
-
- d = D(20)
- self.assertEqual(d.i, 20)
- with self.assertRaises(FrozenInstanceError):
- d.i = 5
- self.assertEqual(d.i, 20)
-
def test_not_tuple(self):
# Test that some of the problems with namedtuple don't happen
# here.
@@ -2475,5 +2452,66 @@ class C(base):
assert False, f'unknown value for expected={expected!r}'
+class TestFrozen(unittest.TestCase):
+ def test_frozen(self):
+ @dataclass(frozen=True)
+ class C:
+ i: int
+
+ c = C(10)
+ self.assertEqual(c.i, 10)
+ with self.assertRaises(FrozenInstanceError):
+ c.i = 5
+ self.assertEqual(c.i, 10)
+
+ def test_inherit(self):
+ @dataclass(frozen=True)
+ class C:
+ i: int
+
+ @dataclass(frozen=True)
+ class D(C):
+ j: int
+
+ d = D(0, 10)
+ with self.assertRaises(FrozenInstanceError):
+ d.i = 5
+ self.assertEqual(d.i, 0)
+
+ def test_inherit_from_nonfrozen_from_frozen(self):
+ @dataclass(frozen=True)
+ class C:
+ i: int
+
+ with self.assertRaisesRegex(TypeError,
+ 'cannot inherit non-frozen dataclass from a frozen one'):
+ @dataclass
+ class D(C):
+ pass
+
+ def test_inherit_from_frozen_from_nonfrozen(self):
+ @dataclass
+ class C:
+ i: int
+
+ with self.assertRaisesRegex(TypeError,
+ 'cannot inherit frozen dataclass from a non-frozen one'):
+ @dataclass(frozen=True)
+ class D(C):
+ pass
+
+ def test_inherit_from_normal_class(self):
+ class C:
+ pass
+
+ @dataclass(frozen=True)
+ class D(C):
+ i: int
+
+ d = D(10)
+ with self.assertRaises(FrozenInstanceError):
+ d.i = 5
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2018-02-26-20-04-40.bpo-32960.48r0Ml.rst b/Misc/NEWS.d/next/Library/2018-02-26-20-04-40.bpo-32960.48r0Ml.rst
new file mode 100644
index 000000000000..4ad1fa17571d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-02-26-20-04-40.bpo-32960.48r0Ml.rst
@@ -0,0 +1,3 @@
+For dataclasses, disallow inheriting frozen from non-frozen classes, and
+also disallow inheriting non-frozen from frozen classes. This restriction
+will be relaxed at a future date.
[View Less]
1
0