mercurial/sslutil.py
changeset 29459 fd93b15b5c30
parent 29449 5b71a8d7f7ff
parent 29452 26a5d605b868
child 29482 4e72995f6c9c
equal deleted inserted replaced
29458:59058549a611 29459:fd93b15b5c30
     9 
     9 
    10 from __future__ import absolute_import
    10 from __future__ import absolute_import
    11 
    11 
    12 import hashlib
    12 import hashlib
    13 import os
    13 import os
       
    14 import re
    14 import ssl
    15 import ssl
    15 import sys
    16 import sys
    16 
    17 
    17 from .i18n import _
    18 from .i18n import _
    18 from . import (
    19 from . import (
   313         'ui': ui,
   314         'ui': ui,
   314     }
   315     }
   315 
   316 
   316     return sslsocket
   317     return sslsocket
   317 
   318 
       
   319 class wildcarderror(Exception):
       
   320     """Represents an error parsing wildcards in DNS name."""
       
   321 
       
   322 def _dnsnamematch(dn, hostname, maxwildcards=1):
       
   323     """Match DNS names according RFC 6125 section 6.4.3.
       
   324 
       
   325     This code is effectively copied from CPython's ssl._dnsname_match.
       
   326 
       
   327     Returns a bool indicating whether the expected hostname matches
       
   328     the value in ``dn``.
       
   329     """
       
   330     pats = []
       
   331     if not dn:
       
   332         return False
       
   333 
       
   334     pieces = dn.split(r'.')
       
   335     leftmost = pieces[0]
       
   336     remainder = pieces[1:]
       
   337     wildcards = leftmost.count('*')
       
   338     if wildcards > maxwildcards:
       
   339         raise wildcarderror(
       
   340             _('too many wildcards in certificate DNS name: %s') % dn)
       
   341 
       
   342     # speed up common case w/o wildcards
       
   343     if not wildcards:
       
   344         return dn.lower() == hostname.lower()
       
   345 
       
   346     # RFC 6125, section 6.4.3, subitem 1.
       
   347     # The client SHOULD NOT attempt to match a presented identifier in which
       
   348     # the wildcard character comprises a label other than the left-most label.
       
   349     if leftmost == '*':
       
   350         # When '*' is a fragment by itself, it matches a non-empty dotless
       
   351         # fragment.
       
   352         pats.append('[^.]+')
       
   353     elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
       
   354         # RFC 6125, section 6.4.3, subitem 3.
       
   355         # The client SHOULD NOT attempt to match a presented identifier
       
   356         # where the wildcard character is embedded within an A-label or
       
   357         # U-label of an internationalized domain name.
       
   358         pats.append(re.escape(leftmost))
       
   359     else:
       
   360         # Otherwise, '*' matches any dotless string, e.g. www*
       
   361         pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
       
   362 
       
   363     # add the remaining fragments, ignore any wildcards
       
   364     for frag in remainder:
       
   365         pats.append(re.escape(frag))
       
   366 
       
   367     pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
       
   368     return pat.match(hostname) is not None
       
   369 
   318 def _verifycert(cert, hostname):
   370 def _verifycert(cert, hostname):
   319     '''Verify that cert (in socket.getpeercert() format) matches hostname.
   371     '''Verify that cert (in socket.getpeercert() format) matches hostname.
   320     CRLs is not handled.
   372     CRLs is not handled.
   321 
   373 
   322     Returns error message if any problems are found and None on success.
   374     Returns error message if any problems are found and None on success.
   323     '''
   375     '''
   324     if not cert:
   376     if not cert:
   325         return _('no certificate received')
   377         return _('no certificate received')
   326     dnsname = hostname.lower()
   378 
   327     def matchdnsname(certname):
   379     dnsnames = []
   328         return (certname == dnsname or
       
   329                 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
       
   330 
       
   331     san = cert.get('subjectAltName', [])
   380     san = cert.get('subjectAltName', [])
   332     if san:
   381     for key, value in san:
   333         certnames = [value.lower() for key, value in san if key == 'DNS']
   382         if key == 'DNS':
   334         for name in certnames:
       
   335             if matchdnsname(name):
       
   336                 return None
       
   337         if certnames:
       
   338             return _('certificate is for %s') % ', '.join(certnames)
       
   339 
       
   340     # subject is only checked when subjectAltName is empty
       
   341     for s in cert.get('subject', []):
       
   342         key, value = s[0]
       
   343         if key == 'commonName':
       
   344             try:
   383             try:
   345                 # 'subject' entries are unicode
   384                 if _dnsnamematch(value, hostname):
   346                 certname = value.lower().encode('ascii')
   385                     return
   347             except UnicodeEncodeError:
   386             except wildcarderror as e:
   348                 return _('IDN in certificate not supported')
   387                 return e.message
   349             if matchdnsname(certname):
   388 
   350                 return None
   389             dnsnames.append(value)
   351             return _('certificate is for %s') % certname
   390 
   352     return _('no commonName or subjectAltName found in certificate')
   391     if not dnsnames:
       
   392         # The subject is only checked when there is no DNS in subjectAltName.
       
   393         for sub in cert.get('subject', []):
       
   394             for key, value in sub:
       
   395                 # According to RFC 2818 the most specific Common Name must
       
   396                 # be used.
       
   397                 if key == 'commonName':
       
   398                     # 'subject' entries are unicide.
       
   399                     try:
       
   400                         value = value.encode('ascii')
       
   401                     except UnicodeEncodeError:
       
   402                         return _('IDN in certificate not supported')
       
   403 
       
   404                     try:
       
   405                         if _dnsnamematch(value, hostname):
       
   406                             return
       
   407                     except wildcarderror as e:
       
   408                         return e.message
       
   409 
       
   410                     dnsnames.append(value)
       
   411 
       
   412     if len(dnsnames) > 1:
       
   413         return _('certificate is for %s') % ', '.join(dnsnames)
       
   414     elif len(dnsnames) == 1:
       
   415         return _('certificate is for %s') % dnsnames[0]
       
   416     else:
       
   417         return _('no commonName or subjectAltName found in certificate')
   353 
   418 
   354 def _plainapplepython():
   419 def _plainapplepython():
   355     """return true if this seems to be a pure Apple Python that
   420     """return true if this seems to be a pure Apple Python that
   356     * is unfrozen and presumably has the whole mercurial module in the file
   421     * is unfrozen and presumably has the whole mercurial module in the file
   357       system
   422       system