Author kiilerix
Recipients Ryan.Tucker, ahasenack, asdfasdfasdfasdfasdfasdfasdf, debatem1, devin, giampaolo.rodola, heikki, janssen, jsamuel, kiilerix, orsenthil, pitrou, vila, zooko
Date 2010-10-04.00:52:47
SpamBayes Score 0.000401284
Marked as misclassified No
Message-id <1286153571.56.0.333502733747.issue1589@psf.upfronthosting.co.za>
In-reply-to
Content
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')
History
Date User Action Args
2010-10-04 00:52:52kiilerixsetrecipients: + kiilerix, zooko, janssen, orsenthil, pitrou, giampaolo.rodola, vila, heikki, ahasenack, debatem1, jsamuel, devin, asdfasdfasdfasdfasdfasdfasdf, Ryan.Tucker
2010-10-04 00:52:51kiilerixsetmessageid: <1286153571.56.0.333502733747.issue1589@psf.upfronthosting.co.za>
2010-10-04 00:52:50kiilerixlinkissue1589 messages
2010-10-04 00:52:47kiilerixcreate