wbond / oscrypto

Compiler-free Python crypto library backed by the OS, supporting CPython and PyPy
MIT License
320 stars 70 forks source link

SNI not working? #50

Closed jsfrerot closed 3 years ago

jsfrerot commented 3 years ago

Hi, I'm trying to validate certificate using SNI (as I have more than 1 certificate for the same vhost in nginx), but it doesn't seem to work properly:

Python 3.6.8 (default, Apr  2 2020, 13:34:55) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from oscrypto import tls 
>>> from certvalidator import CertificateValidator, ValidationContext
>>> session = tls.TLSSession(manual_validation=True)
>>> connection = tls.TLSSocket('10.4.32.37', 443, session=session)
>>> context = ValidationContext(allow_fetching=True)
>>> validator = CertificateValidator(connection.certificate, connection.intermediates, context)
>>> validator.validate_tls('umi-mobile.net')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/data/ludia_default_py36/lib/python3.6/site-packages/certvalidator/__init__.py", line 223, in validate_tls
    validate_tls_hostname(self._context, self._certificate, hostname)
  File "/opt/data/ludia_default_py36/lib/python3.6/site-packages/certvalidator/validate.py", line 95, in validate_tls_hostname
    ', '.join(cert.valid_domains)
certvalidator.errors.InvalidCertificateError: The X.509 certificate provided is not valid for umi-mobile.net. Valid hostnames include: *.ludia.net, ludia.net

and openssl says it's ok

openssl s_client -servername umi-mobile.net -connect 10.4.32.37:443 </dev/null
CONNECTED(00000003)
depth=2 C = US, ST = New Jersey, L = Jersey City, O = The USERTRUST Network, CN = USERTrust RSA Certification Authority
verify return:1
depth=1 C = GB, ST = Greater Manchester, L = Salford, O = Sectigo Limited, CN = Sectigo RSA Domain Validation Secure Server CA
verify return:1
depth=0 CN = *.umi-mobile.net
verify return:1
---
Certificate chain
 0 s:/CN=*.umi-mobile.net
   i:/C=GB/ST=Greater Manchester/L=Salford/O=Sectigo Limited/CN=Sectigo RSA Domain Validation Secure Server CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=Sectigo Limited/CN=Sectigo RSA Domain Validation Secure Server CA
   i:/C=US/ST=New Jersey/L=Jersey City/O=The USERTRUST Network/CN=USERTrust RSA Certification Authority
 2 s:/C=US/ST=New Jersey/L=Jersey City/O=The USERTRUST Network/CN=USERTrust RSA Certification Authority
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
---
...
    Verify return code: 0 (ok)
---
DONE

What am I doing wrong here ?

wbond commented 3 years ago

You aren't telling the TLSSocket what hostname to negotiate since you are passing an IP.

The following:

connection = tls.TLSSocket('10.4.32.37', 443, session=session)

should be:

connection = tls.TLSSocket('umi-mobile.net', 443, session=session)
jsfrerot commented 3 years ago

if I put "umi-mobile.net" it will resolve to the public IP address which is hosted on the haproxy loadbalancer. That's why I need to put the internal IP in the socket. Then I pass the hostname I want to validate in validate_tls. Is there another way of doing this ?

wbond commented 3 years ago

Yes, this should do it:

timeout = 10
connection = tls.TLSSocket.wrap(
    socket.create_connection(('10.4.32.37', 443), timeout),
    'umi-mobile.net',
    session=session
)

This will allow you to create the socket to whatever server you want, then have TLSSocket initiate the TLS handshake using the specified hostname.

jsfrerot commented 3 years ago

That did the trick ! Thank you.