]> granicus.if.org Git - python/commitdiff
Issue #8322: Add a *ciphers* argument to SSL sockets, so as to change the
authorAntoine Pitrou <solipsis@pitrou.net>
Sat, 17 Apr 2010 17:10:38 +0000 (17:10 +0000)
committerAntoine Pitrou <solipsis@pitrou.net>
Sat, 17 Apr 2010 17:10:38 +0000 (17:10 +0000)
available cipher list.  Helps fix test_ssl with OpenSSL 1.0.0.

Doc/library/ssl.rst
Lib/ssl.py
Lib/test/test_ssl.py
Misc/NEWS
Modules/_ssl.c

index 5d8ca3c96bd3f414f225a92bba3f578d5838d0ba..0d19fb9127dc0af6ed4ec318583ae39cf2953943 100644 (file)
@@ -50,7 +50,7 @@ Functions, Constants, and Exceptions
    is a subtype of :exc:`socket.error`, which in turn is a subtype of
    :exc:`IOError`.
 
-.. function:: wrap_socket (sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True)
+.. function:: wrap_socket (sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)
 
    Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance
    of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps
@@ -113,14 +113,23 @@ Functions, Constants, and Exceptions
        ========================  =========  =========  ==========  =========
         *client* / **server**    **SSLv2**  **SSLv3**  **SSLv23**  **TLSv1**
        ------------------------  ---------  ---------  ----------  ---------
-        *SSLv2*                    yes        no         yes*        no
+        *SSLv2*                    yes        no         yes         no
         *SSLv3*                    yes        yes        yes         no
         *SSLv23*                   yes        no         yes         no
         *TLSv1*                    no         no         yes         yes
        ========================  =========  =========  ==========  =========
 
-   In some older versions of OpenSSL (for instance, 0.9.7l on OS X 10.4), an
-   SSLv2 client could not connect to an SSLv23 server.
+   .. note::
+
+      This information varies depending on the version of OpenSSL.
+      For instance, in some older versions of OpenSSL (such as 0.9.7l on
+      OS X 10.4), an SSLv2 client could not connect to an SSLv23 server.
+      Conversely, starting from 1.0.0, an SSLv23 client will actually
+      try the SSLv3 protocol unless you explicitly enable SSLv2 ciphers.
+
+   The parameter ``ciphers`` sets the available ciphers for this SSL object.
+   It should be a string in the `OpenSSL cipher list format
+   <http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT>`_.
 
    The parameter ``do_handshake_on_connect`` specifies whether to do the SSL
    handshake automatically after doing a :meth:`socket.connect`, or whether the
@@ -135,6 +144,9 @@ Functions, Constants, and Exceptions
    normal EOF in response to unexpected EOF errors raised from the underlying
    socket; if :const:`False`, it will raise the exceptions back to the caller.
 
+   .. versionchanged:: 2.7
+      New optional argument *ciphers*.
+
 .. function:: RAND_status()
 
    Returns True if the SSL pseudo-random number generator has been seeded with
index cd1d865c921d506a5ad8af72d9ce7440c7464b38..c960aaa76a79ddd01a4d9349889912b0158e2751 100644 (file)
@@ -89,7 +89,7 @@ class SSLSocket(socket):
                  server_side=False, cert_reqs=CERT_NONE,
                  ssl_version=PROTOCOL_SSLv23, ca_certs=None,
                  do_handshake_on_connect=True,
-                 suppress_ragged_eofs=True):
+                 suppress_ragged_eofs=True, ciphers=None):
         socket.__init__(self, _sock=sock._sock)
         # the initializer for socket trashes the methods (tsk, tsk), so...
         self.send = lambda data, flags=0: SSLSocket.send(self, data, flags)
@@ -111,7 +111,8 @@ class SSLSocket(socket):
             # yes, create the SSL object
             self._sslobj = _ssl.sslwrap(self._sock, server_side,
                                         keyfile, certfile,
-                                        cert_reqs, ssl_version, ca_certs)
+                                        cert_reqs, ssl_version, ca_certs,
+                                        ciphers)
             if do_handshake_on_connect:
                 timeout = self.gettimeout()
                 try:
@@ -124,6 +125,7 @@ class SSLSocket(socket):
         self.cert_reqs = cert_reqs
         self.ssl_version = ssl_version
         self.ca_certs = ca_certs
+        self.ciphers = ciphers
         self.do_handshake_on_connect = do_handshake_on_connect
         self.suppress_ragged_eofs = suppress_ragged_eofs
         self._makefile_refs = 0
@@ -291,7 +293,7 @@ class SSLSocket(socket):
         socket.connect(self, addr)
         self._sslobj = _ssl.sslwrap(self._sock, False, self.keyfile, self.certfile,
                                     self.cert_reqs, self.ssl_version,
-                                    self.ca_certs)
+                                    self.ca_certs, self.ciphers)
         if self.do_handshake_on_connect:
             self.do_handshake()
 
@@ -309,6 +311,7 @@ class SSLSocket(socket):
                           cert_reqs=self.cert_reqs,
                           ssl_version=self.ssl_version,
                           ca_certs=self.ca_certs,
+                          ciphers=self.ciphers,
                           do_handshake_on_connect=self.do_handshake_on_connect,
                           suppress_ragged_eofs=self.suppress_ragged_eofs),
                 addr)
@@ -328,13 +331,14 @@ def wrap_socket(sock, keyfile=None, certfile=None,
                 server_side=False, cert_reqs=CERT_NONE,
                 ssl_version=PROTOCOL_SSLv23, ca_certs=None,
                 do_handshake_on_connect=True,
-                suppress_ragged_eofs=True):
+                suppress_ragged_eofs=True, ciphers=None):
 
     return SSLSocket(sock, keyfile=keyfile, certfile=certfile,
                      server_side=server_side, cert_reqs=cert_reqs,
                      ssl_version=ssl_version, ca_certs=ca_certs,
                      do_handshake_on_connect=do_handshake_on_connect,
-                     suppress_ragged_eofs=suppress_ragged_eofs)
+                     suppress_ragged_eofs=suppress_ragged_eofs,
+                     ciphers=ciphers)
 
 
 # some utility functions
index 0f9822ab85f9f7aa238a65945b9d0fce999545aa..619968585d30bdda8bdcbbe2ac25c0c436ac11d8 100644 (file)
@@ -137,6 +137,23 @@ class BasicTests(unittest.TestCase):
         self.assertTrue(s.startswith("OpenSSL {:d}.{:d}.{:d}".format(major, minor, fix)),
                         (s, t))
 
+    def test_ciphers(self):
+        if not test_support.is_resource_enabled('network'):
+            return
+        remote = ("svn.python.org", 443)
+        s = ssl.wrap_socket(socket.socket(socket.AF_INET),
+                            cert_reqs=ssl.CERT_NONE, ciphers="ALL")
+        s.connect(remote)
+        s = ssl.wrap_socket(socket.socket(socket.AF_INET),
+                            cert_reqs=ssl.CERT_NONE, ciphers="DEFAULT")
+        s.connect(remote)
+        # Error checking occurs when connecting, because the SSL context
+        # isn't created before.
+        s = ssl.wrap_socket(socket.socket(socket.AF_INET),
+                            cert_reqs=ssl.CERT_NONE, ciphers="^$:,;?*'dorothyx")
+        with self.assertRaisesRegexp(ssl.SSLError, "No cipher can be selected"):
+            s.connect(remote)
+
 
 class NetworkedTests(unittest.TestCase):
 
@@ -259,7 +276,8 @@ else:
                                                    certfile=self.server.certificate,
                                                    ssl_version=self.server.protocol,
                                                    ca_certs=self.server.cacerts,
-                                                   cert_reqs=self.server.certreqs)
+                                                   cert_reqs=self.server.certreqs,
+                                                   ciphers=self.server.ciphers)
                 except:
                     if self.server.chatty:
                         handle_error("\n server:  bad connection attempt from " +
@@ -350,7 +368,7 @@ else:
         def __init__(self, certificate, ssl_version=None,
                      certreqs=None, cacerts=None, expect_bad_connects=False,
                      chatty=True, connectionchatty=False, starttls_server=False,
-                     wrap_accepting_socket=False):
+                     wrap_accepting_socket=False, ciphers=None):
 
             if ssl_version is None:
                 ssl_version = ssl.PROTOCOL_TLSv1
@@ -360,6 +378,7 @@ else:
             self.protocol = ssl_version
             self.certreqs = certreqs
             self.cacerts = cacerts
+            self.ciphers = ciphers
             self.expect_bad_connects = expect_bad_connects
             self.chatty = chatty
             self.connectionchatty = connectionchatty
@@ -371,7 +390,8 @@ else:
                                             certfile=self.certificate,
                                             cert_reqs = self.certreqs,
                                             ca_certs = self.cacerts,
-                                            ssl_version = self.protocol)
+                                            ssl_version = self.protocol,
+                                            ciphers = self.ciphers)
                 if test_support.verbose and self.chatty:
                     sys.stdout.write(' server:  wrapped server socket as %s\n' % str(self.sock))
             self.port = test_support.bind_port(self.sock)
@@ -657,13 +677,14 @@ else:
 
     def serverParamsTest (certfile, protocol, certreqs, cacertsfile,
                           client_certfile, client_protocol=None, indata="FOO\n",
-                          chatty=True, connectionchatty=False,
+                          ciphers=None, chatty=True, connectionchatty=False,
                           wrap_accepting_socket=False):
 
         server = ThreadedEchoServer(certfile,
                                     certreqs=certreqs,
                                     ssl_version=protocol,
                                     cacerts=cacertsfile,
+                                    ciphers=ciphers,
                                     chatty=chatty,
                                     connectionchatty=connectionchatty,
                                     wrap_accepting_socket=wrap_accepting_socket)
@@ -679,6 +700,7 @@ else:
                 s = ssl.wrap_socket(socket.socket(),
                                     certfile=client_certfile,
                                     ca_certs=cacertsfile,
+                                    ciphers=ciphers,
                                     cert_reqs=certreqs,
                                     ssl_version=client_protocol)
                 s.connect((HOST, server.port))
@@ -732,8 +754,12 @@ else:
                               ssl.get_protocol_name(server_protocol),
                               certtype))
         try:
+            # NOTE: we must enable "ALL" ciphers, otherwise an SSLv23 client
+            # will send an SSLv3 hello (rather than SSLv2) starting from
+            # OpenSSL 1.0.0 (see issue #8322).
             serverParamsTest(CERTFILE, server_protocol, certsreqs,
-                             CERTFILE, CERTFILE, client_protocol, chatty=False)
+                             CERTFILE, CERTFILE, client_protocol,
+                             ciphers="ALL", chatty=False)
         except test_support.TestFailed:
             if expectedToWork:
                 raise
index 9f1cba17adfbea6a25b043c18438db52975de02f..dfd0b9267cc97fef8bfc8611d34699be1c4178a7 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -18,6 +18,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #8322: Add a *ciphers* argument to SSL sockets, so as to change the
+  available cipher list.  Helps fix test_ssl with OpenSSL 1.0.0.
+
 - Issue #2987: RFC2732 support for urlparse (IPv6 addresses). Patch by Tony
   Locke and Hans Ulrich Niedermann.
 
index 986ec460c6bee6a4a26a6aca689193e8a3e3326c..6dad6a6ff1feba54c2cdcd00e2abdcc7f678a05c 100644 (file)
@@ -259,7 +259,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file,
               enum py_ssl_server_or_client socket_type,
               enum py_ssl_cert_requirements certreq,
               enum py_ssl_version proto_version,
-              char *cacerts_file)
+              char *cacerts_file, char *ciphers)
 {
        PySSLObject *self;
        char *errstr = NULL;
@@ -309,6 +309,14 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file,
                goto fail;
        }
 
+       if (ciphers != NULL) {
+               ret = SSL_CTX_set_cipher_list(self->ctx, ciphers);
+               if (ret == 0) {
+                       errstr = ERRSTR("No cipher can be selected.");
+                       goto fail;
+               }
+       }
+
        if (certreq != PY_SSL_CERT_NONE) {
                if (cacerts_file == NULL) {
                        errstr = ERRSTR("No root certificates specified for "
@@ -408,14 +416,15 @@ PySSL_sslwrap(PyObject *self, PyObject *args)
        char *key_file = NULL;
        char *cert_file = NULL;
        char *cacerts_file = NULL;
+       char *ciphers = NULL;
 
-       if (!PyArg_ParseTuple(args, "O!i|zziiz:sslwrap",
+       if (!PyArg_ParseTuple(args, "O!i|zziizz:sslwrap",
                              PySocketModule.Sock_Type,
                              &Sock,
                              &server_side,
                              &key_file, &cert_file,
                              &verification_mode, &protocol,
-                             &cacerts_file))
+                             &cacerts_file, &ciphers))
                return NULL;
 
        /*
@@ -428,12 +437,13 @@ PySSL_sslwrap(PyObject *self, PyObject *args)
 
        return (PyObject *) newPySSLObject(Sock, key_file, cert_file,
                                           server_side, verification_mode,
-                                          protocol, cacerts_file);
+                                          protocol, cacerts_file,
+                                          ciphers);
 }
 
 PyDoc_STRVAR(ssl_doc,
 "sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n"
-"                              cacertsfile]) -> sslobject");
+"                              cacertsfile, ciphers]) -> sslobject");
 
 /* SSL object methods */