Blame SOURCES/00228-backport-ssl-version.patch

ae2451
ae2451
# HG changeset patch
ae2451
# User Alex Gaynor <alex.gaynor@gmail.com>
ae2451
# Date 1409862802 25200
ae2451
# Node ID 16c86a6bdbe2a545dd2de02dc9f347c2b3ae7220
ae2451
# Parent  f17ab9fed3b03191df975ecdde2cc07cee915319
ae2451
Issue #20421: Add a .version() method to SSL sockets exposing the actual protocol version in use.
ae2451
ae2451
Backport from default.
ae2451
ae2451
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
ae2451
--- a/Doc/library/ssl.rst
ae2451
+++ b/Doc/library/ssl.rst
ae2451
@@ -867,10 +867,10 @@ SSL sockets also have the following addi
ae2451
 
ae2451
 .. method:: SSLSocket.selected_npn_protocol()
ae2451
 
ae2451
-   Returns the protocol that was selected during the TLS/SSL handshake. If
ae2451
-   :meth:`SSLContext.set_npn_protocols` was not called, or if the other party
ae2451
-   does not support NPN, or if the handshake has not yet happened, this will
ae2451
-   return ``None``.
ae2451
+   Returns the higher-level protocol that was selected during the TLS/SSL
ae2451
+   handshake. If :meth:`SSLContext.set_npn_protocols` was not called, or
ae2451
+   if the other party does not support NPN, or if the handshake has not yet
ae2451
+   happened, this will return ``None``.
ae2451
 
ae2451
    .. versionadded:: 2.7.9
ae2451
 
ae2451
@@ -882,6 +882,16 @@ SSL sockets also have the following addi
ae2451
    returned socket should always be used for further communication with the
ae2451
    other side of the connection, rather than the original socket.
ae2451
 
ae2451
+.. method:: SSLSocket.version()
ae2451
+
ae2451
+   Return the actual SSL protocol version negotiated by the connection
ae2451
+   as a string, or ``None`` is no secure connection is established.
ae2451
+   As of this writing, possible return values include ``"SSLv2"``,
ae2451
+   ``"SSLv3"``, ``"TLSv1"``, ``"TLSv1.1"`` and ``"TLSv1.2"``.
ae2451
+   Recent OpenSSL versions may define more return values.
ae2451
+
ae2451
+   .. versionadded:: 3.5
ae2451
+
ae2451
 .. attribute:: SSLSocket.context
ae2451
 
ae2451
    The :class:`SSLContext` object this SSL socket is tied to.  If the SSL
ae2451
diff --git a/Lib/ssl.py b/Lib/ssl.py
ae2451
--- a/Lib/ssl.py
ae2451
+++ b/Lib/ssl.py
ae2451
@@ -862,6 +862,15 @@ class SSLSocket(socket):
ae2451
             return None
ae2451
         return self._sslobj.tls_unique_cb()
ae2451
 
ae2451
+    def version(self):
ae2451
+        """
ae2451
+        Return a string identifying the protocol version used by the
ae2451
+        current SSL channel, or None if there is no established channel.
ae2451
+        """
ae2451
+        if self._sslobj is None:
ae2451
+            return None
ae2451
+        return self._sslobj.version()
ae2451
+
ae2451
 
ae2451
 def wrap_socket(sock, keyfile=None, certfile=None,
ae2451
                 server_side=False, cert_reqs=CERT_NONE,
ae2451
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
ae2451
--- a/Lib/test/test_ssl.py
ae2451
+++ b/Lib/test/test_ssl.py
ae2451
@@ -1904,7 +1904,8 @@ else:
ae2451
                     'compression': s.compression(),
ae2451
                     'cipher': s.cipher(),
ae2451
                     'peercert': s.getpeercert(),
ae2451
-                    'client_npn_protocol': s.selected_npn_protocol()
ae2451
+                    'client_npn_protocol': s.selected_npn_protocol(),
ae2451
+                    'version': s.version(),
ae2451
                 })
ae2451
                 s.close()
ae2451
             stats['server_npn_protocols'] = server.selected_protocols
ae2451
@@ -1912,6 +1913,13 @@ else:
ae2451
 
ae2451
     def try_protocol_combo(server_protocol, client_protocol, expect_success,
ae2451
                            certsreqs=None, server_options=0, client_options=0):
ae2451
+        """
ae2451
+        Try to SSL-connect using *client_protocol* to *server_protocol*.
ae2451
+        If *expect_success* is true, assert that the connection succeeds,
ae2451
+        if it's false, assert that the connection fails.
ae2451
+        Also, if *expect_success* is a string, assert that it is the protocol
ae2451
+        version actually used by the connection.
ae2451
+        """
ae2451
         if certsreqs is None:
ae2451
             certsreqs = ssl.CERT_NONE
ae2451
         certtype = {
ae2451
@@ -1941,8 +1949,8 @@ else:
ae2451
             ctx.load_cert_chain(CERTFILE)
ae2451
             ctx.load_verify_locations(CERTFILE)
ae2451
         try:
ae2451
-            server_params_test(client_context, server_context,
ae2451
-                               chatty=False, connectionchatty=False)
ae2451
+            stats = server_params_test(client_context, server_context,
ae2451
+                                       chatty=False, connectionchatty=False)
ae2451
         # Protocol mismatch can result in either an SSLError, or a
ae2451
         # "Connection reset by peer" error.
ae2451
         except ssl.SSLError:
ae2451
@@ -1957,6 +1965,10 @@ else:
ae2451
                     "Client protocol %s succeeded with server protocol %s!"
ae2451
                     % (ssl.get_protocol_name(client_protocol),
ae2451
                        ssl.get_protocol_name(server_protocol)))
ae2451
+            elif (expect_success is not True
ae2451
+                  and expect_success != stats['version']):
ae2451
+                raise AssertionError("version mismatch: expected %r, got %r"
ae2451
+                                     % (expect_success, stats['version']))
ae2451
 
ae2451
 
ae2451
     class ThreadedTests(unittest.TestCase):
ae2451
@@ -2186,17 +2198,17 @@ else:
ae2451
                         sys.stdout.write(
ae2451
                             " SSL2 client to SSL23 server test unexpectedly failed:\n %s\n"
ae2451
                             % str(x))
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, True)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, 'SSLv3')
ae2451
             try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23, True)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, True)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, 'TLSv1')
ae2451
 
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, True, ssl.CERT_OPTIONAL)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_OPTIONAL)
ae2451
             try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23, True, ssl.CERT_OPTIONAL)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, True, ssl.CERT_OPTIONAL)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
ae2451
 
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, True, ssl.CERT_REQUIRED)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_REQUIRED)
ae2451
             try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23, True, ssl.CERT_REQUIRED)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, True, ssl.CERT_REQUIRED)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
ae2451
 
ae2451
             # Server with specific SSL options
ae2451
             try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv3, False,
ae2451
@@ -2213,9 +2225,9 @@ else:
ae2451
             """Connecting to an SSLv3 server with various client options"""
ae2451
             if support.verbose:
ae2451
                 sys.stdout.write("\n")
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, True)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, True, ssl.CERT_OPTIONAL)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, True, ssl.CERT_REQUIRED)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3')
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_OPTIONAL)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv3, 'SSLv3', ssl.CERT_REQUIRED)
ae2451
             if hasattr(ssl, 'PROTOCOL_SSLv2'):
ae2451
                 try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv2, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv23, False,
ae2451
@@ -2223,7 +2235,7 @@ else:
ae2451
             try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_TLSv1, False)
ae2451
             if no_sslv2_implies_sslv3_hello():
ae2451
                 # No SSLv2 => client will use an SSLv3 hello on recent OpenSSLs
ae2451
-                try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv23, True,
ae2451
+                try_protocol_combo(ssl.PROTOCOL_SSLv3, ssl.PROTOCOL_SSLv23, 'SSLv3',
ae2451
                                    client_options=ssl.OP_NO_SSLv2)
ae2451
 
ae2451
         @skip_if_broken_ubuntu_ssl
ae2451
@@ -2231,9 +2243,9 @@ else:
ae2451
             """Connecting to a TLSv1 server with various client options"""
ae2451
             if support.verbose:
ae2451
                 sys.stdout.write("\n")
ae2451
-            try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, True)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, True, ssl.CERT_OPTIONAL)
ae2451
-            try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, True, ssl.CERT_REQUIRED)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1')
ae2451
+            try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_OPTIONAL)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1, 'TLSv1', ssl.CERT_REQUIRED)
ae2451
             if hasattr(ssl, 'PROTOCOL_SSLv2'):
ae2451
                 try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv2, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_SSLv3, False)
ae2451
@@ -2248,14 +2260,14 @@ else:
ae2451
                Testing against older TLS versions."""
ae2451
             if support.verbose:
ae2451
                 sys.stdout.write("\n")
ae2451
-            try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, True)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1')
ae2451
             if hasattr(ssl, 'PROTOCOL_SSLv2'):
ae2451
                 try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv2, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv3, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_SSLv23, False,
ae2451
                                client_options=ssl.OP_NO_TLSv1_1)
ae2451
 
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_1, True)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_1, 'TLSv1.1')
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_1, False)
ae2451
 
ae2451
@@ -2268,7 +2280,7 @@ else:
ae2451
                Testing against older TLS versions."""
ae2451
             if support.verbose:
ae2451
                 sys.stdout.write("\n")
ae2451
-            try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, True,
ae2451
+            try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2',
ae2451
                                server_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,
ae2451
                                client_options=ssl.OP_NO_SSLv3|ssl.OP_NO_SSLv2,)
ae2451
             if hasattr(ssl, 'PROTOCOL_SSLv2'):
ae2451
@@ -2277,7 +2289,7 @@ else:
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_SSLv23, False,
ae2451
                                client_options=ssl.OP_NO_TLSv1_2)
ae2451
 
ae2451
-            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_2, True)
ae2451
+            try_protocol_combo(ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2')
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_2, False)
ae2451
             try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False)
ae2451
@@ -2619,6 +2631,21 @@ else:
ae2451
                         s.connect((HOST, server.port))
ae2451
             self.assertIn("no shared cipher", str(server.conn_errors[0]))
ae2451
 
ae2451
+        def test_version_basic(self):
ae2451
+            """
ae2451
+            Basic tests for SSLSocket.version().
ae2451
+            More tests are done in the test_protocol_*() methods.
ae2451
+            """
ae2451
+            context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ae2451
+            with ThreadedEchoServer(CERTFILE,
ae2451
+                                    ssl_version=ssl.PROTOCOL_TLSv1,
ae2451
+                                    chatty=False) as server:
ae2451
+                with closing(context.wrap_socket(socket.socket())) as s:
ae2451
+                    self.assertIs(s.version(), None)
ae2451
+                    s.connect((HOST, server.port))
ae2451
+                    self.assertEqual(s.version(), "TLSv1")
ae2451
+                self.assertIs(s.version(), None)
ae2451
+
ae2451
         @unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
ae2451
         def test_default_ecdh_curve(self):
ae2451
             # Issue #21015: elliptic curve-based Diffie Hellman key exchange
ae2451
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
ae2451
--- a/Modules/_ssl.c
ae2451
+++ b/Modules/_ssl.c
ae2451
@@ -1384,6 +1384,18 @@ static PyObject *PySSL_cipher (PySSLSock
ae2451
     return NULL;
ae2451
 }
ae2451
 
ae2451
+static PyObject *PySSL_version(PySSLSocket *self)
ae2451
+{
ae2451
+    const char *version;
ae2451
+
ae2451
+    if (self->ssl == NULL)
ae2451
+        Py_RETURN_NONE;
ae2451
+    version = SSL_get_version(self->ssl);
ae2451
+    if (!strcmp(version, "unknown"))
ae2451
+        Py_RETURN_NONE;
ae2451
+    return PyUnicode_FromString(version);
ae2451
+}
ae2451
+
ae2451
 #ifdef OPENSSL_NPN_NEGOTIATED
ae2451
 static PyObject *PySSL_selected_npn_protocol(PySSLSocket *self) {
ae2451
     const unsigned char *out;
ae2451
@@ -1907,6 +1919,7 @@ static PyMethodDef PySSLMethods[] = {
ae2451
     {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS,
ae2451
      PySSL_peercert_doc},
ae2451
     {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS},
ae2451
+    {"version", (PyCFunction)PySSL_version, METH_NOARGS},
ae2451
 #ifdef OPENSSL_NPN_NEGOTIATED
ae2451
     {"selected_npn_protocol", (PyCFunction)PySSL_selected_npn_protocol, METH_NOARGS},
ae2451
 #endif
ae2451