In the python 2.7.8 to 2.7.9 upgrade, the ssl module changed from using
_DEFAULT_CIPHERS = 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'
to
_DEFAULT_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:ECDH+RC4:'
'DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5'
)
I'd like to know how this affects the actual "ordered SSL cipher preference list" that gets used when establishing SSL/TLS connections with my python installs on Windows.
For example, to figure out what "ordered SSL cipher preference list" a cipher list expands to, I'd normally use the openssl ciphers
command line (see man page) e.g with openssl v1.0.1k I can see what that default python 2.7.8 cipher list expands to:
$ openssl ciphers -v 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384
ECDHE-RSA-AES256-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA1
ECDHE-ECDSA-AES256-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA1
SRP-DSS-AES-256-CBC-SHA SSLv3 Kx=SRP Au=DSS Enc=AES(256) Mac=SHA1
SRP-RSA-AES-256-CBC-SHA SSLv3 Kx=SRP Au=RSA Enc=AES(256) Mac=SHA1
...
snip!
That works great when on Linux where python is dynamically loading the same OpenSSL library that openssl ciphers
uses:
$ ldd /usr/lib/python2.7/lib-dynload/_ssl.x86_64-linux-gnu.so | grep libssl
libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007ff75d6bf000)
$ ldd /usr/bin/openssl | grep libssl
libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007fa48f0fe000)
However, on Windows the Python build appears to statically link the OpenSSL library. This means that the openssl ciphers
command cannot help me, because it uses a different version of the library, which may have support for different ciphers than the library built into python.
I can find out what version of OpenSSL was used to build each of the two python releases easily enough:
$ python-2.7.8/python -c 'import ssl; print ssl.OPENSSL_VERSION'
OpenSSL 1.0.1h 5 Jun 2014
$ python-2.7.9/python -c 'import ssl; print ssl.OPENSSL_VERSION'
OpenSSL 1.0.1j 15 Oct 2014
But even if I could find and download a build of the openssl
command line for both the 1.0.1h and 1.0.1j releases, I cannot be sure that they were compiled with the same options as the lib built into python, and from the man page we know that
Some compiled versions of OpenSSL may not include all the ciphers listed here because some ciphers were excluded at compile time.
So, is there a way to get python's ssl module to give me output similar to that from the openssl ciphers -v
command?
You might want to have a look into openssl cipher
's source code at https://github.com/openssl/openssl/blob/master/apps/ciphers.c
The crucial steps seem to be:
meth = SSLv23_server_method();
ctx = SSL_CTX_new(meth);
SSL_CTX_set_cipher_list(ctx, ciphers)
, whereas ciphers
is your stringssl = SSL_new(ctx);
sk = SSL_get1_supported_ciphers(ssl);
for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) {
print SSL_CIPHER_get_name(sk_SSL_CIPHER_value(sk, i));
}
The SSL_CTX_set_cipher_list
function is called in Python 3.4 in _ssl's set_ciphers
method for contexts. You can achieve the same using:
import socket
from ssl import SSLSocket
sslsock = SSLSocket(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
sslsock.context.set_ciphers('DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2')
The next step would be calling SSL_get1_supported_ciphers()
which, unfortunately, is not used in Python's _ssl.c
. The closest you can get is the shared_ciphers()
method of SSLSocket
instances. The (current) implementation is
static PyObject *PySSL_shared_ciphers(PySSLSocket *self)
{
[...]
ciphers = sess->ciphers;
res = PyList_New(sk_SSL_CIPHER_num(ciphers));
for (i = 0; i < sk_SSL_CIPHER_num(ciphers); i++) {
PyObject *tup = cipher_to_tuple(sk_SSL_CIPHER_value(ciphers, i));
[...]
PyList_SET_ITEM(res, i, tup);
}
return res;
}
That is, this loop is very similar as in the ciphers.c
implementation above, and returns a Python list of ciphers, in the same order as the loop in ciphers.c
would.
Continuing with the sslsock = SSLSocket(...)
example from above, you cannot call sslsock.shared_ciphers()
before the socket is connected. Otherwise, Python's _ssl module does not create a low-level OpenSSL SSL object, which is needed to read the ciphers. That is different from the implementation in ciphers.c
, which creates a low level SSL object without requiring a connection.
That is how far I got, I hope that helps, and maybe you can figure out what you need based on these findings.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With