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

1import time 

2 

3from cryptography import x509 

4from loguru import logger 

5 

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) 

13 

14 

15class Certificates(CryptoParser): 

16 """ 

17 A list of one or more certificates 

18 

19 Attributes: 

20 certificates: a list of 

21 [Certificate][pki_tools.types.certificate.Certificate] 

22 """ 

23 

24 certificates: list[Certificate] 

25 

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. 

32 

33 Args: 

34 crypto_certs: List of cryptography certificates. 

35 

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) 

43 

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. 

49 

50 Args: 

51 file_path: Path and filename of the PEM certificate. 

52 

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() 

58 

59 crypto_certs = x509.load_pem_x509_certificates(cert_pem.encode()) 

60 return cls.from_cryptography(crypto_certs) 

61 

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. 

68 

69 Args: 

70 pem_string: PEM string containing certificate(s). 

71 

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) 

78 

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). 

88 

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') 

97 

98 Returns: 

99 Instance of Certificates containing the certificates 

100 fetched from the URI. 

101 """ 

102 

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) 

108 

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) 

116 

117 return cls(certificates=certificates) 

118 

119 @property 

120 def pem_string(self) -> str: 

121 """ 

122 Returns a string containing the PEM-encoded certificates. 

123 

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 

130 

131 return all_certs 

132 

133 def to_file(self, file_path: str) -> None: 

134 """ 

135 Saves one or more certificate(s) into a file. 

136 

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) 

142 

143 logger.debug(f"Certificate(s) saved to {file_path}") 

144 

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 

150 

151 def _string_dict(self): 

152 certs = {"Certificates": []} 

153 for cert in self.certificates: 

154 certs["Certificates"].append(cert._string_dict()) 

155 return certs 

156 

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