ssl_match_hostname.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # Backport of the match_hostname logic from python 3.5, with small
  2. # changes to support IP address matching on python 2.7 and 3.4.
  3. import re
  4. import sys
  5. try:
  6. # Python 3.4+, or the ipaddress module from pypi.
  7. from ipaddress import ip_address
  8. except ImportError:
  9. ip_address = lambda address: None
  10. # ipaddress.ip_address requires unicode
  11. if sys.version_info[0] < 3:
  12. _unicode = unicode
  13. else:
  14. _unicode = lambda value: value
  15. from pymongo.errors import CertificateError
  16. def _dnsname_match(dn, hostname, max_wildcards=1):
  17. """Matching according to RFC 6125, section 6.4.3
  18. http://tools.ietf.org/html/rfc6125#section-6.4.3
  19. """
  20. pats = []
  21. if not dn:
  22. return False
  23. parts = dn.split(r'.')
  24. leftmost = parts[0]
  25. remainder = parts[1:]
  26. wildcards = leftmost.count('*')
  27. if wildcards > max_wildcards:
  28. # Issue #17980: avoid denials of service by refusing more
  29. # than one wildcard per fragment. A survey of established
  30. # policy among SSL implementations showed it to be a
  31. # reasonable choice.
  32. raise CertificateError(
  33. "too many wildcards in certificate DNS name: " + repr(dn))
  34. # speed up common case w/o wildcards
  35. if not wildcards:
  36. return dn.lower() == hostname.lower()
  37. # RFC 6125, section 6.4.3, subitem 1.
  38. # The client SHOULD NOT attempt to match a presented identifier in which
  39. # the wildcard character comprises a label other than the left-most label.
  40. if leftmost == '*':
  41. # When '*' is a fragment by itself, it matches a non-empty dotless
  42. # fragment.
  43. pats.append('[^.]+')
  44. elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
  45. # RFC 6125, section 6.4.3, subitem 3.
  46. # The client SHOULD NOT attempt to match a presented identifier
  47. # where the wildcard character is embedded within an A-label or
  48. # U-label of an internationalized domain name.
  49. pats.append(re.escape(leftmost))
  50. else:
  51. # Otherwise, '*' matches any dotless string, e.g. www*
  52. pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
  53. # add the remaining fragments, ignore any wildcards
  54. for frag in remainder:
  55. pats.append(re.escape(frag))
  56. pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
  57. return pat.match(hostname)
  58. def _ipaddress_match(ipname, host_ip):
  59. """Exact matching of IP addresses.
  60. RFC 6125 explicitly doesn't define an algorithm for this
  61. (section 1.7.2 - "Out of Scope").
  62. """
  63. # OpenSSL may add a trailing newline to a subjectAltName's IP address
  64. ip = ip_address(_unicode(ipname).rstrip())
  65. return ip == host_ip
  66. def match_hostname(cert, hostname):
  67. """Verify that *cert* (in decoded format as returned by
  68. SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
  69. rules are followed.
  70. CertificateError is raised on failure. On success, the function
  71. returns nothing.
  72. """
  73. if not cert:
  74. raise ValueError("empty or no certificate, match_hostname needs a "
  75. "SSL socket or SSL context with either "
  76. "CERT_OPTIONAL or CERT_REQUIRED")
  77. try:
  78. host_ip = ip_address(_unicode(hostname))
  79. except (ValueError, UnicodeError):
  80. # Not an IP address (common case)
  81. host_ip = None
  82. dnsnames = []
  83. san = cert.get('subjectAltName', ())
  84. for key, value in san:
  85. if key == 'DNS':
  86. if host_ip is None and _dnsname_match(value, hostname):
  87. return
  88. dnsnames.append(value)
  89. elif key == 'IP Address':
  90. if host_ip is not None and _ipaddress_match(value, host_ip):
  91. return
  92. dnsnames.append(value)
  93. if not dnsnames:
  94. # The subject is only checked when there is no dNSName entry
  95. # in subjectAltName
  96. for sub in cert.get('subject', ()):
  97. for key, value in sub:
  98. # XXX according to RFC 2818, the most specific Common Name
  99. # must be used.
  100. if key == 'commonName':
  101. if _dnsname_match(value, hostname):
  102. return
  103. dnsnames.append(value)
  104. if len(dnsnames) > 1:
  105. raise CertificateError("hostname %r "
  106. "doesn't match either of %s"
  107. % (hostname, ', '.join(map(repr, dnsnames))))
  108. elif len(dnsnames) == 1:
  109. raise CertificateError("hostname %r "
  110. "doesn't match %r"
  111. % (hostname, dnsnames[0]))
  112. else:
  113. raise CertificateError("no appropriate commonName or "
  114. "subjectAltName fields were found")