Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/service_identity/pyopenssl.py: 48%
44 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-07 06:38 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-07 06:38 +0000
1"""
2`pyOpenSSL <https://github.com/pyca/pyopenssl>`_-specific code.
3"""
5from __future__ import annotations
7import contextlib
8import warnings
10from typing import Sequence
12from pyasn1.codec.der.decoder import decode
13from pyasn1.type.char import IA5String
14from pyasn1.type.univ import ObjectIdentifier
15from pyasn1_modules.rfc2459 import GeneralNames
17from .exceptions import CertificateError
18from .hazmat import (
19 DNS_ID,
20 CertificatePattern,
21 DNSPattern,
22 IPAddress_ID,
23 IPAddressPattern,
24 SRVPattern,
25 URIPattern,
26 verify_service_identity,
27)
30with contextlib.suppress(ImportError):
31 # We only use it for docstrings -- `if TYPE_CHECKING`` does not work.
32 from OpenSSL.crypto import X509
33 from OpenSSL.SSL import Connection
36__all__ = ["verify_hostname"]
39def verify_hostname(connection: Connection, hostname: str) -> None:
40 r"""
41 Verify whether the certificate of *connection* is valid for *hostname*.
43 Args:
44 connection: A pyOpenSSL connection object.
46 hostname: The hostname that *connection* should be connected to.
48 Raises:
49 service_identity.VerificationError:
50 If *connection* does not provide a certificate that is valid for
51 *hostname*.
53 service_identity.CertificateError:
54 If certificate provided by *connection* contains invalid /
55 unexpected data. This includes the case where the certificate
56 contains no ``subjectAltName``\ s.
58 .. versionchanged:: 24.1.0
59 :exc:`~service_identity.CertificateError` is raised if the certificate
60 contains no ``subjectAltName``\ s instead of
61 :exc:`~service_identity.VerificationError`.
62 """
63 verify_service_identity(
64 cert_patterns=extract_patterns(
65 connection.get_peer_certificate() # type:ignore[arg-type]
66 ),
67 obligatory_ids=[DNS_ID(hostname)],
68 optional_ids=[],
69 )
72def verify_ip_address(connection: Connection, ip_address: str) -> None:
73 r"""
74 Verify whether the certificate of *connection* is valid for *ip_address*.
76 Args:
77 connection: A pyOpenSSL connection object.
79 ip_address:
80 The IP address that *connection* should be connected to. Can be an
81 IPv4 or IPv6 address.
83 Raises:
84 service_identity.VerificationError:
85 If *connection* does not provide a certificate that is valid for
86 *ip_address*.
88 service_identity.CertificateError:
89 If the certificate chain of *connection* contains a certificate
90 that contains invalid/unexpected data.
92 .. versionadded:: 18.1.0
94 .. versionchanged:: 24.1.0
95 :exc:`~service_identity.CertificateError` is raised if the certificate
96 contains no ``subjectAltName``\ s instead of
97 :exc:`~service_identity.VerificationError`.
98 """
99 verify_service_identity(
100 cert_patterns=extract_patterns(
101 connection.get_peer_certificate() # type:ignore[arg-type]
102 ),
103 obligatory_ids=[IPAddress_ID(ip_address)],
104 optional_ids=[],
105 )
108ID_ON_DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") # id_on_dnsSRV
111def extract_patterns(cert: X509) -> Sequence[CertificatePattern]:
112 """
113 Extract all valid ID patterns from a certificate for service verification.
115 Args:
116 cert: The certificate to be dissected.
118 Returns:
119 List of IDs.
121 .. versionchanged:: 23.1.0
122 ``commonName`` is not used as a fallback anymore.
123 """
124 ids: list[CertificatePattern] = []
125 for i in range(cert.get_extension_count()):
126 ext = cert.get_extension(i)
127 if ext.get_short_name() == b"subjectAltName":
128 names, _ = decode(ext.get_data(), asn1Spec=GeneralNames())
129 for n in names:
130 name_string = n.getName()
131 if name_string == "dNSName":
132 ids.append(
133 DNSPattern.from_bytes(n.getComponent().asOctets())
134 )
135 elif name_string == "iPAddress":
136 ids.append(
137 IPAddressPattern.from_bytes(
138 n.getComponent().asOctets()
139 )
140 )
141 elif name_string == "uniformResourceIdentifier":
142 ids.append(
143 URIPattern.from_bytes(n.getComponent().asOctets())
144 )
145 elif name_string == "otherName":
146 comp = n.getComponent()
147 oid = comp.getComponentByPosition(0)
148 if oid == ID_ON_DNS_SRV:
149 srv, _ = decode(comp.getComponentByPosition(1))
150 if isinstance(srv, IA5String):
151 ids.append(SRVPattern.from_bytes(srv.asOctets()))
152 else: # pragma: no cover
153 raise CertificateError(
154 "Unexpected certificate content."
155 )
156 else: # pragma: no cover
157 pass
158 else: # pragma: no cover
159 pass
161 return ids
164def extract_ids(cert: X509) -> Sequence[CertificatePattern]:
165 """
166 Deprecated and never public API. Use :func:`extract_patterns` instead.
168 .. deprecated:: 23.1.0
169 """
170 warnings.warn(
171 category=DeprecationWarning,
172 message="`extract_ids()` is deprecated, please use `extract_patterns()`.",
173 stacklevel=2,
174 )
175 return extract_patterns(cert)