diff -r fc724972f325 Lib/ssl.py --- a/Lib/ssl.py Fri Apr 16 04:46:46 2010 +0200 +++ b/Lib/ssl.py Fri Apr 16 22:41:55 2010 +0200 @@ -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, cert 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 diff -r fc724972f325 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Fri Apr 16 04:46:46 2010 +0200 +++ b/Lib/test/test_ssl.py Fri Apr 16 22:41:55 2010 +0200 @@ -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 diff -r fc724972f325 Modules/_ssl.c --- a/Modules/_ssl.c Fri Apr 16 04:46:46 2010 +0200 +++ b/Modules/_ssl.c Fri Apr 16 22:41:55 2010 +0200 @@ -259,7 +259,7 @@ newPySSLObject(PySocketSockObject *Sock, 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, 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 * 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 * 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 */