Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to list all openssl ciphers available in statically linked python releases?

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?

like image 671
Day Avatar asked Feb 04 '15 22:02

Day


1 Answers

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:

  1. meth = SSLv23_server_method();
  2. ctx = SSL_CTX_new(meth);
  3. SSL_CTX_set_cipher_list(ctx, ciphers), whereas ciphers is your string
  4. ssl = SSL_new(ctx);
  5. sk = SSL_get1_supported_ciphers(ssl);
  6. 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.

like image 150
Dr. Jan-Philip Gehrcke Avatar answered Sep 19 '22 19:09

Dr. Jan-Philip Gehrcke