0

I intend to connect to the remote host example.com over TLS but I have to connect through a proxy IP address with DNS name example-proxy.com.

I don't have control over the SSL certificate and I cannot ask the admin at example.com to add example-proxy.com to its certificate's SAN.

Using example-prxoy.com would cause OpenSSL to error out because the host name does not match the name in the certificate. How can I split the host parameter into two: (1) domain name for the network connection and (2) domain name for the certificate verification.

I don't have the resources to modify the OpenSSL library but I can make changes to the Python libraries. According to this doc, I could have modified the match_hostname method to implement this feature but it is no longer available as of Python 3.7+.

Asks

  1. How can I use Python 3.7+ to specify both a host name and a certificate name?
  2. From the security standpoint, How could my implementation go wrong?
John
  • 3
  • 1
  • The best practice is to create a self-signed CA certificate (technically speaking they are all self signed) and use that CA to sign a certificate for `example.com` and install that on `example-proxy.com`. Then you can install the CA into the trust store of the computer that runs the Python code. – Selcuk May 20 '21 at 01:46
  • I should add that I don't have access to example-proxy.com as well. Additionally, example-proxy is an L3 TCP/IP proxy and it doesn't intervene in the TLS handshake. I also don't have network admin access and I can't alter the DNS records. – John May 20 '21 at 04:45
  • I see, yes, with a TCP proxy you can't really do anything. Are you using `requests` or plain `urllib`? See if [this answer](https://stackoverflow.com/a/22794281/2011147) helps. – Selcuk May 20 '21 at 04:50
  • I directly use the SSLSocket from the ssl package. – John May 20 '21 at 04:55
  • What about [SSLContext.check_hostname](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.check_hostname) then? – Selcuk May 20 '21 at 04:57
  • It is true that I can set `check_hostname=False` but it would bypass the certificate verification measure which would make the connection vulnerable to man-in-the-middle attacks. My intention is to maintain security by verifying the domain name that is on the certificate. – John May 20 '21 at 05:04

1 Answers1

0

Just give a different hostname for TCP connection and TLS handshake, i.e. set server_hostname in wrap_socket. To modify the example from the official documentation for this:

import socket
import ssl

tls_hostname = 'www.example.com'
context = ssl.create_default_context()

with socket.create_connection(('127.0.0.1',8443)) as sock:
    with context.wrap_socket(sock, server_hostname=tls_hostname) as ssock:
        print(ssock.version())

This will connect to ('127.0.0.1',8443) but do the TLS handshake with www.example.com. Note that this will use tls_hostname for both SNI extension in the TLS handshake and for validating the certificate. But this seems to be what you need based on your question anyway: connect to IP:port but do TLS handshake and validation with a specific hostname.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • Thanks! It worked. Do you see any potential security vulnerabilities that might arise from this config? – John May 21 '21 at 06:36
  • @John: The point of the hostname verification is that the certificate matches the expected hostname in order to protect against active man in the middle attacks. The expectation here is clearly defined. This means that there is no security problem introduced by this. – Steffen Ullrich May 21 '21 at 06:39