ssl_match_hostname.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. # Backport of the match_hostname logic introduced in python 3.2
  2. # http://hg.python.org/releasing/3.3.5/file/993955b807b3/Lib/ssl.py
  3. import re
  4. class CertificateError(ValueError):
  5. pass
  6. def _dnsname_match(dn, hostname, max_wildcards=1):
  7. """Matching according to RFC 6125, section 6.4.3
  8. http://tools.ietf.org/html/rfc6125#section-6.4.3
  9. """
  10. pats = []
  11. if not dn:
  12. return False
  13. parts = dn.split(r'.')
  14. leftmost = parts[0]
  15. remainder = parts[1:]
  16. wildcards = leftmost.count('*')
  17. if wildcards > max_wildcards:
  18. # Issue #17980: avoid denials of service by refusing more
  19. # than one wildcard per fragment. A survey of established
  20. # policy among SSL implementations showed it to be a
  21. # reasonable choice.
  22. raise CertificateError(
  23. "too many wildcards in certificate DNS name: " + repr(dn))
  24. # speed up common case w/o wildcards
  25. if not wildcards:
  26. return dn.lower() == hostname.lower()
  27. # RFC 6125, section 6.4.3, subitem 1.
  28. # The client SHOULD NOT attempt to match a presented identifier in which
  29. # the wildcard character comprises a label other than the left-most label.
  30. if leftmost == '*':
  31. # When '*' is a fragment by itself, it matches a non-empty dotless
  32. # fragment.
  33. pats.append('[^.]+')
  34. elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
  35. # RFC 6125, section 6.4.3, subitem 3.
  36. # The client SHOULD NOT attempt to match a presented identifier
  37. # where the wildcard character is embedded within an A-label or
  38. # U-label of an internationalized domain name.
  39. pats.append(re.escape(leftmost))
  40. else:
  41. # Otherwise, '*' matches any dotless string, e.g. www*
  42. pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
  43. # add the remaining fragments, ignore any wildcards
  44. for frag in remainder:
  45. pats.append(re.escape(frag))
  46. pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
  47. return pat.match(hostname)
  48. def match_hostname(cert, hostname):
  49. """Verify that *cert* (in decoded format as returned by
  50. SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
  51. rules are followed, but IP addresses are not accepted for *hostname*.
  52. CertificateError is raised on failure. On success, the function
  53. returns nothing.
  54. """
  55. if not cert:
  56. raise ValueError("empty or no certificate")
  57. dnsnames = []
  58. san = cert.get('subjectAltName', ())
  59. for key, value in san:
  60. if key == 'DNS':
  61. if _dnsname_match(value, hostname):
  62. return
  63. dnsnames.append(value)
  64. if not dnsnames:
  65. # The subject is only checked when there is no dNSName entry
  66. # in subjectAltName
  67. for sub in cert.get('subject', ()):
  68. for key, value in sub:
  69. # XXX according to RFC 2818, the most specific Common Name
  70. # must be used.
  71. if key == 'commonName':
  72. if _dnsname_match(value, hostname):
  73. return
  74. dnsnames.append(value)
  75. if len(dnsnames) > 1:
  76. raise CertificateError("hostname %r "
  77. "doesn't match either of %s"
  78. % (hostname, ', '.join(map(repr, dnsnames))))
  79. elif len(dnsnames) == 1:
  80. raise CertificateError("hostname %r "
  81. "doesn't match %r"
  82. % (hostname, dnsnames[0]))
  83. else:
  84. raise CertificateError("no appropriate commonName or "
  85. "subjectAltName fields were found")