Commit 0a6373c1 authored by Antoine Pitrou's avatar Antoine Pitrou

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.
parent 7794b5b5
...@@ -50,7 +50,7 @@ Functions, Constants, and Exceptions ...@@ -50,7 +50,7 @@ Functions, Constants, and Exceptions
is a subtype of :exc:`socket.error`, which in turn is a subtype of is a subtype of :exc:`socket.error`, which in turn is a subtype of
:exc:`IOError`. :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 Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance
of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps
...@@ -113,14 +113,23 @@ Functions, Constants, and Exceptions ...@@ -113,14 +113,23 @@ Functions, Constants, and Exceptions
======================== ========= ========= ========== ========= ======================== ========= ========= ========== =========
*client* / **server** **SSLv2** **SSLv3** **SSLv23** **TLSv1** *client* / **server** **SSLv2** **SSLv3** **SSLv23** **TLSv1**
------------------------ --------- --------- ---------- --------- ------------------------ --------- --------- ---------- ---------
*SSLv2* yes no yes* no *SSLv2* yes no yes no
*SSLv3* yes yes yes no *SSLv3* yes yes yes no
*SSLv23* yes no yes no *SSLv23* yes no yes no
*TLSv1* no no yes yes *TLSv1* no no yes yes
======================== ========= ========= ========== ========= ======================== ========= ========= ========== =========
In some older versions of OpenSSL (for instance, 0.9.7l on OS X 10.4), an .. note::
SSLv2 client could not connect to an SSLv23 server.
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 The parameter ``do_handshake_on_connect`` specifies whether to do the SSL
handshake automatically after doing a :meth:`socket.connect`, or whether the handshake automatically after doing a :meth:`socket.connect`, or whether the
...@@ -135,6 +144,9 @@ Functions, Constants, and Exceptions ...@@ -135,6 +144,9 @@ Functions, Constants, and Exceptions
normal EOF in response to unexpected EOF errors raised from the underlying 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. socket; if :const:`False`, it will raise the exceptions back to the caller.
.. versionchanged:: 2.7
New optional argument *ciphers*.
.. function:: RAND_status() .. function:: RAND_status()
Returns True if the SSL pseudo-random number generator has been seeded with Returns True if the SSL pseudo-random number generator has been seeded with
......
...@@ -89,7 +89,7 @@ class SSLSocket(socket): ...@@ -89,7 +89,7 @@ class SSLSocket(socket):
server_side=False, cert_reqs=CERT_NONE, server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_SSLv23, ca_certs=None, ssl_version=PROTOCOL_SSLv23, ca_certs=None,
do_handshake_on_connect=True, do_handshake_on_connect=True,
suppress_ragged_eofs=True): suppress_ragged_eofs=True, ciphers=None):
socket.__init__(self, _sock=sock._sock) socket.__init__(self, _sock=sock._sock)
# the initializer for socket trashes the methods (tsk, tsk), so... # the initializer for socket trashes the methods (tsk, tsk), so...
self.send = lambda data, flags=0: SSLSocket.send(self, data, flags) self.send = lambda data, flags=0: SSLSocket.send(self, data, flags)
...@@ -111,7 +111,8 @@ class SSLSocket(socket): ...@@ -111,7 +111,8 @@ class SSLSocket(socket):
# yes, create the SSL object # yes, create the SSL object
self._sslobj = _ssl.sslwrap(self._sock, server_side, self._sslobj = _ssl.sslwrap(self._sock, server_side,
keyfile, certfile, keyfile, certfile,
cert_reqs, ssl_version, ca_certs) cert_reqs, ssl_version, ca_certs,
ciphers)
if do_handshake_on_connect: if do_handshake_on_connect:
timeout = self.gettimeout() timeout = self.gettimeout()
try: try:
...@@ -124,6 +125,7 @@ class SSLSocket(socket): ...@@ -124,6 +125,7 @@ class SSLSocket(socket):
self.cert_reqs = cert_reqs self.cert_reqs = cert_reqs
self.ssl_version = ssl_version self.ssl_version = ssl_version
self.ca_certs = ca_certs self.ca_certs = ca_certs
self.ciphers = ciphers
self.do_handshake_on_connect = do_handshake_on_connect self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs self.suppress_ragged_eofs = suppress_ragged_eofs
self._makefile_refs = 0 self._makefile_refs = 0
...@@ -291,7 +293,7 @@ class SSLSocket(socket): ...@@ -291,7 +293,7 @@ class SSLSocket(socket):
socket.connect(self, addr) socket.connect(self, addr)
self._sslobj = _ssl.sslwrap(self._sock, False, self.keyfile, self.certfile, self._sslobj = _ssl.sslwrap(self._sock, False, self.keyfile, self.certfile,
self.cert_reqs, self.ssl_version, self.cert_reqs, self.ssl_version,
self.ca_certs) self.ca_certs, self.ciphers)
if self.do_handshake_on_connect: if self.do_handshake_on_connect:
self.do_handshake() self.do_handshake()
...@@ -309,6 +311,7 @@ class SSLSocket(socket): ...@@ -309,6 +311,7 @@ class SSLSocket(socket):
cert_reqs=self.cert_reqs, cert_reqs=self.cert_reqs,
ssl_version=self.ssl_version, ssl_version=self.ssl_version,
ca_certs=self.ca_certs, ca_certs=self.ca_certs,
ciphers=self.ciphers,
do_handshake_on_connect=self.do_handshake_on_connect, do_handshake_on_connect=self.do_handshake_on_connect,
suppress_ragged_eofs=self.suppress_ragged_eofs), suppress_ragged_eofs=self.suppress_ragged_eofs),
addr) addr)
...@@ -328,13 +331,14 @@ def wrap_socket(sock, keyfile=None, certfile=None, ...@@ -328,13 +331,14 @@ def wrap_socket(sock, keyfile=None, certfile=None,
server_side=False, cert_reqs=CERT_NONE, server_side=False, cert_reqs=CERT_NONE,
ssl_version=PROTOCOL_SSLv23, ca_certs=None, ssl_version=PROTOCOL_SSLv23, ca_certs=None,
do_handshake_on_connect=True, do_handshake_on_connect=True,
suppress_ragged_eofs=True): suppress_ragged_eofs=True, ciphers=None):
return SSLSocket(sock, keyfile=keyfile, certfile=certfile, return SSLSocket(sock, keyfile=keyfile, certfile=certfile,
server_side=server_side, cert_reqs=cert_reqs, server_side=server_side, cert_reqs=cert_reqs,
ssl_version=ssl_version, ca_certs=ca_certs, ssl_version=ssl_version, ca_certs=ca_certs,
do_handshake_on_connect=do_handshake_on_connect, 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 # some utility functions
......
...@@ -137,6 +137,23 @@ class BasicTests(unittest.TestCase): ...@@ -137,6 +137,23 @@ class BasicTests(unittest.TestCase):
self.assertTrue(s.startswith("OpenSSL {:d}.{:d}.{:d}".format(major, minor, fix)), self.assertTrue(s.startswith("OpenSSL {:d}.{:d}.{:d}".format(major, minor, fix)),
(s, t)) (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): class NetworkedTests(unittest.TestCase):
...@@ -259,7 +276,8 @@ else: ...@@ -259,7 +276,8 @@ else:
certfile=self.server.certificate, certfile=self.server.certificate,
ssl_version=self.server.protocol, ssl_version=self.server.protocol,
ca_certs=self.server.cacerts, ca_certs=self.server.cacerts,
cert_reqs=self.server.certreqs) cert_reqs=self.server.certreqs,
ciphers=self.server.ciphers)
except: except:
if self.server.chatty: if self.server.chatty:
handle_error("\n server: bad connection attempt from " + handle_error("\n server: bad connection attempt from " +
...@@ -350,7 +368,7 @@ else: ...@@ -350,7 +368,7 @@ else:
def __init__(self, certificate, ssl_version=None, def __init__(self, certificate, ssl_version=None,
certreqs=None, cacerts=None, expect_bad_connects=False, certreqs=None, cacerts=None, expect_bad_connects=False,
chatty=True, connectionchatty=False, starttls_server=False, chatty=True, connectionchatty=False, starttls_server=False,
wrap_accepting_socket=False): wrap_accepting_socket=False, ciphers=None):
if ssl_version is None: if ssl_version is None:
ssl_version = ssl.PROTOCOL_TLSv1 ssl_version = ssl.PROTOCOL_TLSv1
...@@ -360,6 +378,7 @@ else: ...@@ -360,6 +378,7 @@ else:
self.protocol = ssl_version self.protocol = ssl_version
self.certreqs = certreqs self.certreqs = certreqs
self.cacerts = cacerts self.cacerts = cacerts
self.ciphers = ciphers
self.expect_bad_connects = expect_bad_connects self.expect_bad_connects = expect_bad_connects
self.chatty = chatty self.chatty = chatty
self.connectionchatty = connectionchatty self.connectionchatty = connectionchatty
...@@ -371,7 +390,8 @@ else: ...@@ -371,7 +390,8 @@ else:
certfile=self.certificate, certfile=self.certificate,
cert_reqs = self.certreqs, cert_reqs = self.certreqs,
ca_certs = self.cacerts, ca_certs = self.cacerts,
ssl_version = self.protocol) ssl_version = self.protocol,
ciphers = self.ciphers)
if test_support.verbose and self.chatty: if test_support.verbose and self.chatty:
sys.stdout.write(' server: wrapped server socket as %s\n' % str(self.sock)) sys.stdout.write(' server: wrapped server socket as %s\n' % str(self.sock))
self.port = test_support.bind_port(self.sock) self.port = test_support.bind_port(self.sock)
...@@ -657,13 +677,14 @@ else: ...@@ -657,13 +677,14 @@ else:
def serverParamsTest (certfile, protocol, certreqs, cacertsfile, def serverParamsTest (certfile, protocol, certreqs, cacertsfile,
client_certfile, client_protocol=None, indata="FOO\n", client_certfile, client_protocol=None, indata="FOO\n",
chatty=True, connectionchatty=False, ciphers=None, chatty=True, connectionchatty=False,
wrap_accepting_socket=False): wrap_accepting_socket=False):
server = ThreadedEchoServer(certfile, server = ThreadedEchoServer(certfile,
certreqs=certreqs, certreqs=certreqs,
ssl_version=protocol, ssl_version=protocol,
cacerts=cacertsfile, cacerts=cacertsfile,
ciphers=ciphers,
chatty=chatty, chatty=chatty,
connectionchatty=connectionchatty, connectionchatty=connectionchatty,
wrap_accepting_socket=wrap_accepting_socket) wrap_accepting_socket=wrap_accepting_socket)
...@@ -679,6 +700,7 @@ else: ...@@ -679,6 +700,7 @@ else:
s = ssl.wrap_socket(socket.socket(), s = ssl.wrap_socket(socket.socket(),
certfile=client_certfile, certfile=client_certfile,
ca_certs=cacertsfile, ca_certs=cacertsfile,
ciphers=ciphers,
cert_reqs=certreqs, cert_reqs=certreqs,
ssl_version=client_protocol) ssl_version=client_protocol)
s.connect((HOST, server.port)) s.connect((HOST, server.port))
...@@ -732,8 +754,12 @@ else: ...@@ -732,8 +754,12 @@ else:
ssl.get_protocol_name(server_protocol), ssl.get_protocol_name(server_protocol),
certtype)) certtype))
try: 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, serverParamsTest(CERTFILE, server_protocol, certsreqs,
CERTFILE, CERTFILE, client_protocol, chatty=False) CERTFILE, CERTFILE, client_protocol,
ciphers="ALL", chatty=False)
except test_support.TestFailed: except test_support.TestFailed:
if expectedToWork: if expectedToWork:
raise raise
......
...@@ -18,6 +18,9 @@ Core and Builtins ...@@ -18,6 +18,9 @@ Core and Builtins
Library 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 - Issue #2987: RFC2732 support for urlparse (IPv6 addresses). Patch by Tony
Locke and Hans Ulrich Niedermann. Locke and Hans Ulrich Niedermann.
......
...@@ -259,7 +259,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_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_server_or_client socket_type,
enum py_ssl_cert_requirements certreq, enum py_ssl_cert_requirements certreq,
enum py_ssl_version proto_version, enum py_ssl_version proto_version,
char *cacerts_file) char *cacerts_file, char *ciphers)
{ {
PySSLObject *self; PySSLObject *self;
char *errstr = NULL; char *errstr = NULL;
...@@ -309,6 +309,14 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, ...@@ -309,6 +309,14 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file,
goto fail; 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 (certreq != PY_SSL_CERT_NONE) {
if (cacerts_file == NULL) { if (cacerts_file == NULL) {
errstr = ERRSTR("No root certificates specified for " errstr = ERRSTR("No root certificates specified for "
...@@ -408,14 +416,15 @@ PySSL_sslwrap(PyObject *self, PyObject *args) ...@@ -408,14 +416,15 @@ PySSL_sslwrap(PyObject *self, PyObject *args)
char *key_file = NULL; char *key_file = NULL;
char *cert_file = NULL; char *cert_file = NULL;
char *cacerts_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, PySocketModule.Sock_Type,
&Sock, &Sock,
&server_side, &server_side,
&key_file, &cert_file, &key_file, &cert_file,
&verification_mode, &protocol, &verification_mode, &protocol,
&cacerts_file)) &cacerts_file, &ciphers))
return NULL; return NULL;
/* /*
...@@ -428,12 +437,13 @@ PySSL_sslwrap(PyObject *self, PyObject *args) ...@@ -428,12 +437,13 @@ PySSL_sslwrap(PyObject *self, PyObject *args)
return (PyObject *) newPySSLObject(Sock, key_file, cert_file, return (PyObject *) newPySSLObject(Sock, key_file, cert_file,
server_side, verification_mode, server_side, verification_mode,
protocol, cacerts_file); protocol, cacerts_file,
ciphers);
} }
PyDoc_STRVAR(ssl_doc, PyDoc_STRVAR(ssl_doc,
"sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n" "sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n"
" cacertsfile]) -> sslobject"); " cacertsfile, ciphers]) -> sslobject");
/* SSL object methods */ /* SSL object methods */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment