do_handshake resets cipher list (server side)

Asked by Israel Nir on 2013-08-20

Hi folks, you are my last hope!
I'm trying to force my server to use 'RC4-SHA' for debugging reasons.
I first do
ctx.set_options(OP_CIPHER_SERVER_PREFERENCE)
ctx.set_cipher_list('RC4-SHA')

and everything looks great, that is,

connection = SSL.Connection(ctx, connection)
connection.set_accept_state()
print "before", connection.get_cipher_list()

prints "before ['RC4-SHA']"

However, next I call do_handshake:

connection.do_handshake()
print "after", connection.get_cipher_list()

and it prints the default cipher list. Moreover, in Wireshark I can see that my server's reply chose a cipher other than 'RC4-SHA', even though the client handshake declared that it supports it.

Am I doing something wrong, or is this a bug?

I'm using OS.X 10.6.8 with Python 2.7.5, OpenSSL 1.0.1e and pyOpenSSL 0.13

Any help will be greatly appreciated.

Question information

Language:
English Edit question
Status:
Solved
For:
pyOpenSSL Edit question
Assignee:
No assignee Edit question
Solved by:
Israel Nir
Solved:
2013-08-20
Last query:
2013-08-20
Last reply:
2013-08-20
Jean-Paul Calderone (exarkun) said : #1

Consider including an <http://sscce.org/> rather than just a few fragments of a program.

Also, I haven't noticed a post on the mailing list about this topic yet, so you have at least one more forum to which to resort (ie, Launchpad bugs needn't be your last hope).

Israel Nir (israeln) said : #2

Thanks Jean-Paul,

The code is actually part of mitmproxy's netlib (https://github.com/mitmproxy/netlib).
Here's the full method, but I think my few fragments are more "sscce" in this case :)

    def convert_to_ssl(self, cert, key, method=SSLv23_METHOD, options=None, handle_sni=None, request_client_cert=False, cipher_list=None):
        ctx = SSL.Context(method)
        if not options is None:
            ctx.set_options(options)
        ctx.set_options(OP_CIPHER_SERVER_PREFERENCE)
        if cipher_list:
            ctx.set_cipher_list(cipher_list)
        if handle_sni:
            # SNI callback happens during do_handshake()
            ctx.set_tlsext_servername_callback(handle_sni)
        ctx.use_privatekey_file(key)
        ctx.use_certificate(cert.x509)
        if request_client_cert:
            def ver(*args):
                self.clientcert = certutils.SSLCert(args[1])
            ctx.set_verify(SSL.VERIFY_PEER, ver)
        self.connection = SSL.Connection(ctx, self.connection)
        self.ssl_established = True
        self.connection.set_accept_state()
        try:
            print "before", self.connection.getpeername(), self.connection.get_cipher_list()
            self.connection.do_handshake()
            print "after", self.connection.getpeername(), self.connection.get_cipher_list()
        except SSL.Error, v:
            raise NetLibError("SSL handshake error: %s"%str(v))
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)

Jean-Paul Calderone (exarkun) said : #3

I might run an SSCCE in a few environments to try to reproduce the problem and see if I can understand why it happens. I'm not going to try to write a program that reproduces the problem, though.

Israel Nir (israeln) said : #4

You are absolutely right.
I had to overcome my fear of openssl, and make some demo code. While doing so, I've discovered the problem.

In the callback defined by

ctx.set_tlsext_servername_callback(handle_sni)

No cipher list was defined. I guess that once this callback is called during the handshake process, previous configuration is reset.