简体   繁体   中英

Handling SNI with pyOpenSSL - Python

I'm working with pyOpenSSL lately, however I came across some urls that use SNI to present multiple certificates for the same IP address. Here's my code:

from OpenSSL import SSL
from socket import socket
from sys import argv, stdout
import re
from urlparse import urlparse

def callback(conn, cert, errno, depth, result):
    if depth == 0 and (errno == 9 or errno == 10):
        return False # or raise Exception("Certificate not yet valid or expired")
    return True

def main():
    if len(argv) < 2:
            print 'Usage: %s <hostname>' % (argv[0],)
            return 1

    o = urlparse(argv[1])
    host_name = o.netloc
    context = SSL.Context(SSL.TLSv1_METHOD) # Use TLS Method
    context.set_options(SSL.OP_NO_SSLv2) # Don't accept SSLv2
    context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
                       callback)
    # context.load_verify_locations(ca_file, ca_path)

    sock = socket()
    ssl_sock = SSL.Connection(context, sock)
    ssl_sock.connect((host_name, 443))
    ssl_sock.do_handshake()

    cert = ssl_sock.get_peer_certificate()
    common_name = cert.get_subject().commonName.decode()
    print "Common Name: ", common_name
    print "Cert number: ", cert.get_serial_number()
    regex = common_name.replace('.', r'\.').replace('*',r'.*') + '$'
    if re.match(regex, host_name):
        print "matches"
    else:
        print "invalid"

if __name__ == "__main__":
    main()

For example, let's say I have the following url:

https://example.com

When I get the following output:

python sni.py https://example.com/
Common Name:  *.example.com
Cert number:  63694395280496902491340707875731768741
invalid

which is the same certificate for https://another.example.com :

python sni.py https://another.example.com/
Common Name:  *.example.com
Cert number:  63694395280496902491340707875731768741
matches

However, let's say, the certificate for https://another.example.com is expired, the connection will be accepted anyways, since it's using the *.example.com certificate, which is valid. However I want to be able to use https://another.example.com/ and if it's not valid, reject the connection outright. How can I accomplish that?

You need to use set_tlsext_host_name . From the documentation :

Connection.set_tlsext_host_name(name)
  Specify the byte string to send as the server name in the client hello message.
  New in version 0.13.

Apart from that your hostname validation is wrong since it only compares against the CN and not the subject alternative names. Also it allows wildcards on any place which is against the rule that wildcards should only be allowed in the leftmost label: *.example.com is fine while www.*.com or even *.*.* is not allowed but accepted by your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM