Coverage for pki_tools/types/certificates.py: 76%
67 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 time
3from cryptography import x509
4from loguru import logger
6from pki_tools.types.certificate import Certificate
7from pki_tools.types.crypto_parser import CryptoParser
8from pki_tools.types.utils import (
9 CACHE_TIME_SECONDS,
10 CertsUri,
11 _download_cached,
12)
15class Certificates(CryptoParser):
16 """
17 A list of one or more certificates
19 Attributes:
20 certificates: a list of
21 [Certificate][pki_tools.types.certificate.Certificate]
22 """
24 certificates: list[Certificate]
26 @classmethod
27 def from_cryptography(
28 cls: type["Certificates"], crypto_certs: list[x509.Certificate]
29 ) -> "Certificates":
30 """
31 Create a Certificates object from a list of cryptography certificates.
33 Args:
34 crypto_certs: List of cryptography certificates.
36 Returns:
37 Instance of Certificates containing the provided certificates.
38 """
39 certificates = []
40 for crypt_cert in crypto_certs:
41 certificates.append(Certificate.from_cryptography(crypt_cert))
42 return cls(certificates=certificates, _x509_obj=crypto_certs)
44 @classmethod
45 def from_file(cls: type["Certificates"], file_path: str) -> "Certificates":
46 """
47 Reads a file containing one or more PEM certificate(s) into a
48 Certificates object.
50 Args:
51 file_path: Path and filename of the PEM certificate.
53 Returns:
54 A Certificates object representing the certificate(s) from file.
55 """
56 with open(file_path, "r") as f:
57 cert_pem = f.read()
59 crypto_certs = x509.load_pem_x509_certificates(cert_pem.encode())
60 return cls.from_cryptography(crypto_certs)
62 @classmethod
63 def from_pem_string(
64 cls: type["Certificates"], pem_string: str
65 ) -> "Certificates":
66 """
67 Create a Certificates object from a PEM string.
69 Args:
70 pem_string: PEM string containing certificate(s).
72 Returns:
73 Instance of Certificates containing the certificates from the PEM
74 string.
75 """
76 crypto_certs = x509.load_pem_x509_certificates(pem_string.encode())
77 return cls.from_cryptography(crypto_certs)
79 @classmethod
80 def from_uri(
81 cls: type["Certificates"],
82 uris: list[str],
83 cache_time_seconds: int = CACHE_TIME_SECONDS,
84 proxy: str | None = None,
85 ) -> "Certificates":
86 """
87 Loads Certificates from one or more URI(s).
89 Args:
90 uris: One or more URI(s) where the certificate(s) can
91 be downloaded.
92 cache_time_seconds: Specifies how long the certificates
93 should be cached, default is 1 month.
94 Defaults to CACHE_TIME_SECONDS.
95 proxy: Optional proxy URL to use for the HTTP request
96 (e.g., 'http://proxy.example.com:8080')
98 Returns:
99 Instance of Certificates containing the certificates
100 fetched from the URI.
101 """
103 certificates = []
104 cache_ttl = round(time.time() / cache_time_seconds)
105 for uri in uris:
106 cert_uri = CertsUri(uri=uri)
107 res = _download_cached(cert_uri.uri, cache_ttl, proxy)
109 try:
110 # Try PEM first
111 certificates += cls.from_pem_string(res.text).certificates
112 except ValueError:
113 # Fallback to DER
114 cert = Certificate.from_der_bytes(res.content)
115 certificates.append(cert)
117 return cls(certificates=certificates)
119 @property
120 def pem_string(self) -> str:
121 """
122 Returns a string containing the PEM-encoded certificates.
124 Returns:
125 PEM string containing all the certificates.
126 """
127 all_certs = ""
128 for cert in self.certificates:
129 all_certs += cert.pem_string
131 return all_certs
133 def to_file(self, file_path: str) -> None:
134 """
135 Saves one or more certificate(s) into a file.
137 Args:
138 file_path: Path and filename where to store the certificate(s).
139 """
140 with open(file_path, "w") as f:
141 f.write(self.pem_string)
143 logger.debug(f"Certificate(s) saved to {file_path}")
145 def _to_cryptography(self) -> list[x509.Certificate]:
146 certs = []
147 for cert in self.certificates:
148 certs.append(cert._to_cryptography())
149 return certs
151 def _string_dict(self):
152 certs = {"Certificates": []}
153 for cert in self.certificates:
154 certs["Certificates"].append(cert._string_dict())
155 return certs
157 def __str__(self):
158 ret = ""
159 count = 0
160 for cert in self.certificates:
161 count += 1
162 ret += f"{'-' * 10}Certificate #{count}{'-' * 10}\n"
163 ret += str(cert)
164 ret += f"{'-' * 34}\n"
165 ret += f"Chain certificate count: {count}"
166 return ret