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

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