4

I'm using http.client.HTTPSConnection to generate an HTTPS connection to my server. I cannot use the original hostname to connect to the server since this is a test setup but I need to do a proper TLS handshake with the right hostname as SNI. How do I set the SNI for the client hello of the HTTPS connection?

Judging from ssl.SSLSocket.server_hostname, if I could access the underlying socket I should be able to set server_hostname on it to the value I want. HTTPSConnection does indeed have a sock member, but it is None after construction.

In case more source code context is helpful, I'm working on the test proxy in proxy-verifier. See proxy_http1.py#L94

6
  • 1
    The SNI is automatically set from the hostname you use, i.e. no need to set it explicitly. If you need to behave it otherwise then please explain the underlying problem since maybe you are trying to solve the wrong Y of an XY problem. Commented Apr 17, 2020 at 22:29
  • A traffic dump shows no SNI in the client hello. Commented Apr 17, 2020 at 23:08
  • A traffic dump shows for me clearly that SNI is used based on the hostname given in HTTPSConnection, as it is also expected from looking at the code. Of course, there has to be actually a hostname given and no IP address. If you feel otherwise please provide a minimal, reproducible example with enough details to reproduce your problem (i.e. version of Python, OS, ...). Commented Apr 18, 2020 at 5:13
  • I see what you're saying. By hostname, you are referring to the host parameter passed to HTTPSConnection, the first parameter. In my case that is an IP address and must be. Well, "must" is a strong word, but for this setup it's not easy for me to modify DNS behavior to target my test box from arbitrary hostnames. But I require, independent of that, to specify the SNI for the TLS handshake. That sounds like a reasonable thing to desire and, indeed, this test requires it. Is there a way to make that happen with HTTPSConnection? Thanks for the dialog. Commented Apr 18, 2020 at 17:07
  • 1
    I've changed your question to explain why you cannot use the builtin mechanism to set SNI. I don't think that this is trivial. I could basically see two ways: either hijack the socket creation so that you can use the hostname in the URL but it will connect instead to your specific IP address. Or provide a custom SSL context which has a fixed setting for the hostname instead of using the provided one. Commented Apr 18, 2020 at 18:08

1 Answer 1

6

Steffen Ullrich guided me to the answer. There is no direct support to pass two different host names a) to connect to and b) to send via SNI and verify the certificate against. However, http.client.HTTPSConnection calls the ssl.SSLContext.wrap_socket function off of the SSLContext passed in. This code example leverages this with a simple wrapper:

hostname_connect = '192.168.1.3'
hostname_sni = 'www.example.com'

class WrapSSLContext(ssl.SSLContext):
    def wrap_socket(self, *args, **kwargs):
        kwargs['server_hostname'] = hostname_sni
        return super().wrap_socket(*args, **kwargs)

context = WrapSSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_default_certs()
connection = http.client.HTTPSConnection(hostname_connect, context=context)

Contexts created with ssl.PROTOCOL_TLS_CLIENT have context.check_hostname set by default. And thus, context.verify_mode = ssl.CERT_REQUIRED as well.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.