Coverage for pki_tools/crl.py: 95%
81 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-11-02 22:00 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-11-02 22:00 +0000
1import socket
2from urllib.parse import urlparse
4from loguru import logger
6from pki_tools.exceptions import (
7 CrlIdpInvalid,
8 ExtensionMissing,
9 FetchFailure,
10 LoadError,
11)
12from pki_tools.types.certificate import Certificate
13from pki_tools.types.chain import Chain
14from pki_tools.types.crl import CertificateRevocationList
15from pki_tools.types.extensions import UniformResourceIdentifier
18def _compare_cdp_and_idp(
19 cdp_uri: str, idp_uri: str, same_crl_domains: list[list[str]] | None = None
20):
21 log = logger.bind(
22 cdp=cdp_uri,
23 idp=idp_uri,
24 )
26 parsed_cdp = urlparse(cdp_uri)
27 parsed_idp = urlparse(idp_uri)
29 if (
30 parsed_cdp.path.rstrip("/").split("/")[-1]
31 != parsed_idp.path.rstrip("/").split("/")[-1]
32 ):
33 log.warning("CRL IDP path is not the same as cert CDP")
34 return False
36 if parsed_cdp.scheme != parsed_idp.scheme:
37 log.warning("CRL IDP scheme is not the same as cert CDP")
38 return False
40 if same_crl_domains is not None:
41 for domain_list in same_crl_domains:
42 if (
43 parsed_cdp.hostname in domain_list
44 and parsed_idp.hostname in domain_list
45 ):
46 log.debug(
47 "CRL IDP and cert CDP are considered to be the same domain"
48 )
49 return True
51 if parsed_cdp.hostname != parsed_idp.hostname:
52 try:
53 cdp_ips = set(socket.gethostbyname_ex(parsed_cdp.hostname)[2])
54 idp_ips = set(socket.gethostbyname_ex(parsed_idp.hostname)[2])
55 except socket.gaierror:
56 log.warning(
57 "CRL IDP or cert CDP hostname not resolvable and differ"
58 )
59 return False
61 if not (cdp_ips <= idp_ips or idp_ips <= cdp_ips):
62 # Not (All elements of one set are in the other)
63 log.bind(
64 cdp_ips=",".join(cdp_ips),
65 idp_ips=",".join(idp_ips),
66 ).warning("CRL IDP hostname is not the same as cert CDP")
67 return False
69 return True
72def _is_revoked(
73 cert: Certificate,
74 crl_issuer: Chain,
75 crl_cache_seconds: int = 3600,
76 same_crl_domains: list[list[str]] | None = None,
77) -> bool:
78 crl_issuer.validate_trust_path(cert)
79 logger.debug("CRL issuer chain valid for cert")
81 log = logger.bind(
82 cert=cert.pem_string,
83 serial=cert.hex_serial,
84 )
86 if cert.extensions.crl_distribution_points is None:
87 log.debug("CRL extension missing")
88 raise ExtensionMissing()
90 http_dist = False
91 fetched_crl = False
92 last_fetched_crl_exception = None
94 for dist_point in cert.extensions.crl_distribution_points:
95 if dist_point.full_name is None:
96 continue
98 for full_name in dist_point.full_name:
99 if not isinstance(full_name, UniformResourceIdentifier):
100 log.warning(
101 "CRL Distribution Point is not UniformResourceIdentifier"
102 )
103 continue
105 uri = full_name.value
107 http_dist = True
109 try:
110 crl = CertificateRevocationList.from_uri(
111 uri, cache_time_seconds=crl_cache_seconds
112 )
113 except (LoadError, FetchFailure) as e:
114 log.warning("Failed to fetch CRL from uri")
115 last_fetched_crl_exception = e
116 continue
118 fetched_crl = True
120 issuer = crl_issuer.get_issuer(crl)
122 issuer.verify_signature(crl)
123 log.debug("CRL signature valid")
125 if (
126 crl.extensions is not None
127 and crl.extensions.issuing_distribution_point is not None
128 and crl.extensions.issuing_distribution_point.full_name
129 is not None
130 ):
131 log.debug("CRL Issuing Distribution Point extension present")
132 full_names = (
133 crl.extensions.issuing_distribution_point.full_name
134 )
136 for crl_idp in full_names:
137 if _compare_cdp_and_idp(
138 uri, crl_idp.value, same_crl_domains=same_crl_domains
139 ):
140 break
141 else:
142 log.error("CRL IDP and cert CDP differ")
143 raise CrlIdpInvalid()
145 if (r := crl.get_revoked(cert.serial_number)) is not None:
146 log.bind(date=str(r.date)).debug("Certificate revoked")
147 return True
149 if not http_dist:
150 log.debug("CRL missing URI")
151 raise ExtensionMissing()
153 if not fetched_crl and isinstance(last_fetched_crl_exception, Exception):
154 raise last_fetched_crl_exception
156 log.debug("Certificate valid")
157 return False