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

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