Merged revisions 80151 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r80151 | antoine.pitrou | 2010-04-17 19:10:38 +0200 (sam., 17 avril 2010) | 4 lines

  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.
........
This commit is contained in:
Antoine Pitrou 2010-04-17 17:40:45 +00:00
parent ec8dfeb27e
commit 2d9cb9c1cb
5 changed files with 72 additions and 17 deletions

View file

@ -47,7 +47,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
@ -110,14 +110,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
@ -132,6 +141,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

View file

@ -94,7 +94,7 @@ class SSLSocket(socket):
ssl_version=PROTOCOL_SSLv23, ca_certs=None, ssl_version=PROTOCOL_SSLv23, ca_certs=None,
do_handshake_on_connect=True, do_handshake_on_connect=True,
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
suppress_ragged_eofs=True): suppress_ragged_eofs=True, ciphers=None):
if sock is not None: if sock is not None:
socket.__init__(self, socket.__init__(self,
@ -123,7 +123,8 @@ class SSLSocket(socket):
try: try:
self._sslobj = _ssl.sslwrap(self, server_side, self._sslobj = _ssl.sslwrap(self, 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()
if timeout == 0.0: if timeout == 0.0:
@ -140,6 +141,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
@ -325,7 +327,7 @@ class SSLSocket(socket):
socket.connect(self, addr) socket.connect(self, addr)
self._sslobj = _ssl.sslwrap(self, False, self.keyfile, self.certfile, self._sslobj = _ssl.sslwrap(self, 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)
try: try:
if self.do_handshake_on_connect: if self.do_handshake_on_connect:
self.do_handshake() self.do_handshake()
@ -345,6 +347,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= do_handshake_on_connect=
self.do_handshake_on_connect), self.do_handshake_on_connect),
addr) addr)
@ -358,13 +361,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=sock, keyfile=keyfile, certfile=certfile, return SSLSocket(sock=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

View file

@ -121,6 +121,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 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):
@ -234,7 +251,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 " + repr(self.addr) + ":\n") handle_error("\n server: bad connection attempt from " + repr(self.addr) + ":\n")
@ -333,7 +351,8 @@ 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,
ciphers=None):
if ssl_version is None: if ssl_version is None:
ssl_version = ssl.PROTOCOL_TLSv1 ssl_version = ssl.PROTOCOL_TLSv1
if certreqs is None: if certreqs is None:
@ -342,6 +361,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
@ -648,12 +668,13 @@ else:
def serverParamsTest (certfile, protocol, certreqs, cacertsfile, def serverParamsTest (certfile, protocol, certreqs, cacertsfile,
client_certfile, client_protocol=None, client_certfile, client_protocol=None,
indata="FOO\n", indata="FOO\n",
chatty=False, connectionchatty=False): ciphers=None, chatty=False, connectionchatty=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=False) connectionchatty=False)
flag = threading.Event() flag = threading.Event()
@ -669,6 +690,7 @@ else:
certfile=client_certfile, certfile=client_certfile,
ca_certs=cacertsfile, ca_certs=cacertsfile,
cert_reqs=certreqs, cert_reqs=certreqs,
ciphers=ciphers,
ssl_version=client_protocol) ssl_version=client_protocol)
s.connect((HOST, server.port)) s.connect((HOST, server.port))
except ssl.SSLError as x: except ssl.SSLError as x:
@ -723,8 +745,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, CERTFILE, CERTFILE, client_protocol,
ciphers="ALL",
chatty=False, connectionchatty=False) chatty=False, connectionchatty=False)
except support.TestFailed: except support.TestFailed:
if expectedToWork: if expectedToWork:

View file

@ -315,6 +315,9 @@ C-API
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 #8393: subprocess accepts bytes, bytearray and str with surrogates for - Issue #8393: subprocess accepts bytes, bytearray and str with surrogates for
the current working directory. the current working directory.

View file

@ -262,7 +262,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;
@ -310,6 +310,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)
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)
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 */