# HG changeset patch # User Mads Kiilerich # Date 2011-01-11 01:48:58 # Node ID 1a4330e300177a833898a41bce96564dff93a17d # Parent af50a62e9c2040dcdaf61ba6a6400bb45ab56410 # Parent 75d0c38a0bca3501aaa3fa6b8c26de8ca0270803 merge with stable diff --git a/mercurial/url.py b/mercurial/url.py --- a/mercurial/url.py +++ b/mercurial/url.py @@ -506,22 +506,38 @@ class httphandler(keepalive.HTTPHandler) def _verifycert(cert, hostname): '''Verify that cert (in socket.getpeercert() format) matches hostname. - CRLs and subjectAltName are not handled. + CRLs is not handled. Returns error message if any problems are found and None on success. ''' if not cert: return _('no certificate received') dnsname = hostname.lower() + def matchdnsname(certname): + return (certname == dnsname or + '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]) + + san = cert.get('subjectAltName', []) + if san: + certnames = [value.lower() for key, value in san if key == 'DNS'] + for name in certnames: + if matchdnsname(name): + return None + return _('certificate is for %s') % ', '.join(certnames) + + # subject is only checked when subjectAltName is empty 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]): + try: + # 'subject' entries are unicode + certname = value.lower().encode('ascii') + except UnicodeEncodeError: + return _('IDN in certificate not supported') + if matchdnsname(certname): return None return _('certificate is for %s') % certname - return _('no commonName found in certificate') + return _('no commonName or subjectAltName found in certificate') if has_https: class BetterHTTPS(httplib.HTTPSConnection): diff --git a/tests/test-url.py b/tests/test-url.py --- a/tests/test-url.py +++ b/tests/test-url.py @@ -25,6 +25,18 @@ check(_verifycert(cert('*.example.com'), check(_verifycert(cert('*.example.com'), 'w.w.example.com'), 'certificate is for *.example.com') +# Test subjectAltName +san_cert = {'subject': ((('commonName', 'example.com'),),), + 'subjectAltName': (('DNS', '*.example.net'), + ('DNS', 'example.net'))} +check(_verifycert(san_cert, 'example.net'), + None) +check(_verifycert(san_cert, 'foo.example.net'), + None) +# subject is only checked when subjectAltName is empty +check(_verifycert(san_cert, 'example.com'), + 'certificate is for *.example.net, example.net') + # Avoid some pitfalls check(_verifycert(cert('*.foo'), 'foo'), 'certificate is for *.foo') @@ -33,6 +45,10 @@ check(_verifycert(cert('*o'), 'foo'), check(_verifycert({'subject': ()}, 'example.com'), - 'no commonName found in certificate') + 'no commonName or subjectAltName found in certificate') check(_verifycert(None, 'example.com'), 'no certificate received') + +# Unicode (IDN) certname isn't supported +check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'), + 'IDN in certificate not supported')