184 lines
5.2 KiB
Python
184 lines
5.2 KiB
Python
![]() |
"""
|
||
|
`cryptography.x509 <https://github.com/pyca/cryptography>`_-specific code.
|
||
|
"""
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
import warnings
|
||
|
|
||
|
from typing import Sequence
|
||
|
|
||
|
from cryptography.x509 import (
|
||
|
Certificate,
|
||
|
DNSName,
|
||
|
ExtensionOID,
|
||
|
IPAddress,
|
||
|
ObjectIdentifier,
|
||
|
OtherName,
|
||
|
UniformResourceIdentifier,
|
||
|
)
|
||
|
from cryptography.x509.extensions import ExtensionNotFound
|
||
|
from pyasn1.codec.der.decoder import decode
|
||
|
from pyasn1.type.char import IA5String
|
||
|
|
||
|
from .exceptions import CertificateError
|
||
|
from .hazmat import (
|
||
|
DNS_ID,
|
||
|
CertificatePattern,
|
||
|
DNSPattern,
|
||
|
IPAddress_ID,
|
||
|
IPAddressPattern,
|
||
|
SRVPattern,
|
||
|
URIPattern,
|
||
|
verify_service_identity,
|
||
|
)
|
||
|
|
||
|
|
||
|
__all__ = ["verify_certificate_hostname"]
|
||
|
|
||
|
|
||
|
def verify_certificate_hostname(
|
||
|
certificate: Certificate, hostname: str
|
||
|
) -> None:
|
||
|
r"""
|
||
|
Verify whether *certificate* is valid for *hostname*.
|
||
|
|
||
|
.. note::
|
||
|
Nothing is verified about the *authority* of the certificate;
|
||
|
the caller must verify that the certificate chains to an appropriate
|
||
|
trust root themselves.
|
||
|
|
||
|
Args:
|
||
|
certificate: A *cryptography* X509 certificate object.
|
||
|
|
||
|
hostname: The hostname that *certificate* should be valid for.
|
||
|
|
||
|
Raises:
|
||
|
service_identity.VerificationError:
|
||
|
If *certificate* is not valid for *hostname*.
|
||
|
|
||
|
service_identity.CertificateError:
|
||
|
If *certificate* contains invalid / unexpected data. This includes
|
||
|
the case where the certificate contains no `subjectAltName`\ s.
|
||
|
|
||
|
.. versionchanged:: 24.1.0
|
||
|
:exc:`~service_identity.CertificateError` is raised if the certificate
|
||
|
contains no ``subjectAltName``\ s instead of
|
||
|
:exc:`~service_identity.VerificationError`.
|
||
|
"""
|
||
|
verify_service_identity(
|
||
|
cert_patterns=extract_patterns(certificate),
|
||
|
obligatory_ids=[DNS_ID(hostname)],
|
||
|
optional_ids=[],
|
||
|
)
|
||
|
|
||
|
|
||
|
def verify_certificate_ip_address(
|
||
|
certificate: Certificate, ip_address: str
|
||
|
) -> None:
|
||
|
r"""
|
||
|
Verify whether *certificate* is valid for *ip_address*.
|
||
|
|
||
|
.. note::
|
||
|
Nothing is verified about the *authority* of the certificate;
|
||
|
the caller must verify that the certificate chains to an appropriate
|
||
|
trust root themselves.
|
||
|
|
||
|
Args:
|
||
|
certificate: A *cryptography* X509 certificate object.
|
||
|
|
||
|
ip_address:
|
||
|
The IP address that *connection* should be valid for. Can be an
|
||
|
IPv4 or IPv6 address.
|
||
|
|
||
|
Raises:
|
||
|
service_identity.VerificationError:
|
||
|
If *certificate* is not valid for *ip_address*.
|
||
|
|
||
|
service_identity.CertificateError:
|
||
|
If *certificate* contains invalid / unexpected data. This includes
|
||
|
the case where the certificate contains no ``subjectAltName``\ s.
|
||
|
|
||
|
.. versionadded:: 18.1.0
|
||
|
|
||
|
.. versionchanged:: 24.1.0
|
||
|
:exc:`~service_identity.CertificateError` is raised if the certificate
|
||
|
contains no ``subjectAltName``\ s instead of
|
||
|
:exc:`~service_identity.VerificationError`.
|
||
|
"""
|
||
|
verify_service_identity(
|
||
|
cert_patterns=extract_patterns(certificate),
|
||
|
obligatory_ids=[IPAddress_ID(ip_address)],
|
||
|
optional_ids=[],
|
||
|
)
|
||
|
|
||
|
|
||
|
ID_ON_DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") # id_on_dnsSRV
|
||
|
|
||
|
|
||
|
def extract_patterns(cert: Certificate) -> Sequence[CertificatePattern]:
|
||
|
"""
|
||
|
Extract all valid ID patterns from a certificate for service verification.
|
||
|
|
||
|
Args:
|
||
|
cert: The certificate to be dissected.
|
||
|
|
||
|
Returns:
|
||
|
List of IDs.
|
||
|
|
||
|
.. versionchanged:: 23.1.0
|
||
|
``commonName`` is not used as a fallback anymore.
|
||
|
"""
|
||
|
ids: list[CertificatePattern] = []
|
||
|
try:
|
||
|
ext = cert.extensions.get_extension_for_oid(
|
||
|
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
|
||
|
)
|
||
|
except ExtensionNotFound:
|
||
|
pass
|
||
|
else:
|
||
|
ids.extend(
|
||
|
[
|
||
|
DNSPattern.from_bytes(name.encode("utf-8"))
|
||
|
for name in ext.value.get_values_for_type(DNSName)
|
||
|
]
|
||
|
)
|
||
|
ids.extend(
|
||
|
[
|
||
|
URIPattern.from_bytes(uri.encode("utf-8"))
|
||
|
for uri in ext.value.get_values_for_type(
|
||
|
UniformResourceIdentifier
|
||
|
)
|
||
|
]
|
||
|
)
|
||
|
ids.extend(
|
||
|
[
|
||
|
IPAddressPattern(ip)
|
||
|
for ip in ext.value.get_values_for_type(IPAddress)
|
||
|
]
|
||
|
)
|
||
|
for other in ext.value.get_values_for_type(OtherName):
|
||
|
if other.type_id == ID_ON_DNS_SRV:
|
||
|
srv, _ = decode(other.value)
|
||
|
if isinstance(srv, IA5String):
|
||
|
ids.append(SRVPattern.from_bytes(srv.asOctets()))
|
||
|
else: # pragma: no cover
|
||
|
msg = "Unexpected certificate content."
|
||
|
raise CertificateError(msg)
|
||
|
|
||
|
return ids
|
||
|
|
||
|
|
||
|
def extract_ids(cert: Certificate) -> Sequence[CertificatePattern]:
|
||
|
"""
|
||
|
Deprecated and never public API. Use :func:`extract_patterns` instead.
|
||
|
|
||
|
.. deprecated:: 23.1.0
|
||
|
"""
|
||
|
warnings.warn(
|
||
|
category=DeprecationWarning,
|
||
|
message="`extract_ids()` is deprecated, please use `extract_patterns()`.",
|
||
|
stacklevel=2,
|
||
|
)
|
||
|
return extract_patterns(cert)
|