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

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