--- a/mercurial/url.py Sat Jan 08 22:15:19 2011 -0600
+++ b/mercurial/url.py Tue Jan 11 02:48:58 2011 +0100
@@ -506,22 +506,38 @@
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):
--- a/tests/test-url.py Sat Jan 08 22:15:19 2011 -0600
+++ b/tests/test-url.py Tue Jan 11 02:48:58 2011 +0100
@@ -25,6 +25,18 @@
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({'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')