[issue1589] New SSL module doesn't seem to verify hostname against commonName in certificate

Mads Kiilerich report at bugs.python.org
Mon Oct 4 02:52:51 CEST 2010


Mads Kiilerich <mads at kiilerich.com> added the comment:

I added some extra verification to Mercurial (http://www.selenic.com/hg/rev/f2937d6492c5). Feel free to use the following under the Python license in Python or elsewhere. It could be a separate method/function or it could integrated in wrap_socket and controlled by a keyword. I would appreciate if you find the implementation insufficient or incorrect.

The purpose with this function is to verify if the received and validated certificate matches the host we intended to connect to.

I try to keep it simple and to fail to the safe side. 

"Correct" subjectAltName handling seems not to be feasible.

Are CRLs checked by the SSL module? Otherwise it deserves a big fat warning.

(I now assume that notBefore is handled by the SSL module and shouldn't be checked here.)

def _verifycert(cert, hostname):
    '''Verify that cert (in socket.getpeercert() format) matches
    hostname and is valid at this time. CRLs and subjectAltName are
    not handled.
    
    Returns error message if any problems are found and None on success.
    '''
    if not cert:
        return _('no certificate received')
    notafter = cert.get('notAfter')
    if notafter and time.time() > ssl.cert_time_to_seconds(notafter):
        return _('certificate expired %s') % notafter
    dnsname = hostname.lower()
    for s in cert.get('subject', []):
        key, value = s[0]
        if key == 'commonName':
            certname = value.lower()
            if (certname == dnsname or
                '.' in dnsname and
                certname == '*.' + dnsname.split('.', 1)[1]):
                return None
            return _('certificate is for %s') % certname
    return _('no commonName found in certificate')


def check(a, b):
    if a != b:
        print (a, b)

# Test non-wildcard certificates        
check(_verifycert({'subject': ((('commonName', 'example.com'),),)}, 'example.com'),
    None)
check(_verifycert({'subject': ((('commonName', 'example.com'),),)}, 'www.example.com'),
    'certificate is for example.com')
check(_verifycert({'subject': ((('commonName', 'www.example.com'),),)}, 'example.com'),
    'certificate is for www.example.com')

# Test wildcard certificates
check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'www.example.com'),
    None)
check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'example.com'),
    'certificate is for *.example.com')
check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'w.w.example.com'),
    'certificate is for *.example.com')

# Avoid some pitfalls
check(_verifycert({'subject': ((('commonName', '*.foo'),),)}, 'foo'),
    'certificate is for *.foo')
check(_verifycert({'subject': ((('commonName', '*o'),),)}, 'foo'),
    'certificate is for *o')

import time
lastyear = time.gmtime().tm_year - 1
nextyear = time.gmtime().tm_year + 1
check(_verifycert({'notAfter': 'May  9 00:00:00 %s GMT' % lastyear}, 'example.com'),
    'certificate expired May  9 00:00:00 %s GMT' % lastyear)
check(_verifycert({'notAfter': 'Sep 29 15:29:48 %s GMT' % nextyear, 'subject': ()}, 'example.com'),
    'no commonName found in certificate')
check(_verifycert(None, 'example.com'),
    'no certificate received')

----------
nosy: +kiilerix

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue1589>
_______________________________________


More information about the Python-bugs-list mailing list