pyopenssl.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. """
  2. `pyOpenSSL <https://github.com/pyca/pyopenssl>`_-specific code.
  3. """
  4. from __future__ import absolute_import, division, print_function
  5. import warnings
  6. from pyasn1.codec.der.decoder import decode
  7. from pyasn1.type.char import IA5String
  8. from pyasn1.type.univ import ObjectIdentifier
  9. from pyasn1_modules.rfc2459 import GeneralNames
  10. from .exceptions import SubjectAltNameWarning
  11. from ._common import (
  12. CertificateError,
  13. DNSPattern,
  14. DNS_ID,
  15. SRVPattern,
  16. URIPattern,
  17. verify_service_identity,
  18. )
  19. __all__ = [
  20. "verify_hostname",
  21. ]
  22. def verify_hostname(connection, hostname):
  23. """
  24. Verify whether the certificate of *connection* is valid for *hostname*.
  25. :param OpenSSL.SSL.Connection connection: A pyOpenSSL connection object.
  26. :param unicode hostname: The hostname that *connection* should be connected
  27. to.
  28. :raises service_identity.VerificationError: If *connection* does not
  29. provide a certificate that is valid for *hostname*.
  30. :raises service_identity.CertificateError: If the certificate chain of
  31. *connection* contains a certificate that contains invalid/unexpected
  32. data.
  33. :returns: ``None``
  34. """
  35. verify_service_identity(
  36. cert_patterns=extract_ids(connection.get_peer_certificate()),
  37. obligatory_ids=[DNS_ID(hostname)],
  38. optional_ids=[],
  39. )
  40. ID_ON_DNS_SRV = ObjectIdentifier('1.3.6.1.5.5.7.8.7') # id_on_dnsSRV
  41. def extract_ids(cert):
  42. """
  43. Extract all valid IDs from a certificate for service verification.
  44. If *cert* doesn't contain any identifiers, the ``CN``s are used as DNS-IDs
  45. as fallback.
  46. :param OpenSSL.SSL.X509 cert: The certificate to be dissected.
  47. :return: List of IDs.
  48. """
  49. ids = []
  50. for i in range(cert.get_extension_count()):
  51. ext = cert.get_extension(i)
  52. if ext.get_short_name() == b"subjectAltName":
  53. names, _ = decode(ext.get_data(), asn1Spec=GeneralNames())
  54. for n in names:
  55. name_string = n.getName()
  56. if name_string == "dNSName":
  57. ids.append(DNSPattern(n.getComponent().asOctets()))
  58. elif name_string == "uniformResourceIdentifier":
  59. ids.append(URIPattern(n.getComponent().asOctets()))
  60. elif name_string == "otherName":
  61. comp = n.getComponent()
  62. oid = comp.getComponentByPosition(0)
  63. if oid == ID_ON_DNS_SRV:
  64. srv, _ = decode(comp.getComponentByPosition(1))
  65. if isinstance(srv, IA5String):
  66. ids.append(SRVPattern(srv.asOctets()))
  67. else: # pragma: nocover
  68. raise CertificateError(
  69. "Unexpected certificate content."
  70. )
  71. if not ids:
  72. # http://tools.ietf.org/search/rfc6125#section-6.4.4
  73. # A client MUST NOT seek a match for a reference identifier of CN-ID if
  74. # the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or any
  75. # application-specific identifier types supported by the client.
  76. components = [c[1]
  77. for c
  78. in cert.get_subject().get_components()
  79. if c[0] == b"CN"]
  80. cn = next(iter(components), b'<not given>')
  81. ids = [DNSPattern(c) for c in components]
  82. warnings.warn(
  83. "Certificate with CN '{}' has no `subjectAltName`, falling back "
  84. "to check for a `commonName` for now. This feature is being "
  85. "removed by major browsers and deprecated by RFC 2818. "
  86. "service_identity will remove the support for it in mid-2018."
  87. .format(cn.decode("utf-8")),
  88. SubjectAltNameWarning
  89. )
  90. return ids