[Python-checkins] bpo-31453: Add setter for min/max protocol version (GH-5259)

Miss Islington (bot) webhook-mailer at python.org
Tue Feb 27 06:41:08 EST 2018


https://github.com/python/cpython/commit/4c842b09209ccf1b4f853106b1f58bb888da02ef
commit: 4c842b09209ccf1b4f853106b1f58bb888da02ef
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: GitHub <noreply at 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 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 at python.org>
(cherry picked from commit 698dde16f60729d9e3f53c23a4ddb8e5ffe818bf)

Co-authored-by: Christian Heimes <christian at 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();



More information about the Python-checkins mailing list