Coverage for tests/admin/test_certificate_v2_element_x509.py: 100%

186 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-10-30 06:22 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2021 RSK Labs Ltd 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy of 

6# this software and associated documentation files (the "Software"), to deal in 

7# the Software without restriction, including without limitation the rights to 

8# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 

9# of the Software, and to permit persons to whom the Software is furnished to do 

10# so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in all 

13# copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

21# SOFTWARE. 

22 

23from unittest import TestCase 

24from unittest.mock import Mock, patch 

25from ecdsa import NIST256p 

26from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding 

27from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1 

28from admin.certificate_v2 import HSMCertificateV2Element, \ 

29 HSMCertificateV2ElementX509 

30from .test_certificate_v2_resources import TEST_CERTIFICATE 

31 

32 

33class TestHSMCertificateV2ElementX509(TestCase): 

34 def setUp(self): 

35 self.elem = HSMCertificateV2ElementX509({ 

36 "name": "thename", 

37 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", 

38 "signed_by": "whosigned", 

39 }) 

40 

41 def test_props(self): 

42 self.assertEqual("thename", self.elem.name) 

43 self.assertEqual("whosigned", self.elem.signed_by) 

44 self.assertEqual("dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", self.elem.message) 

45 

46 def test_dict_ok(self): 

47 self.assertEqual({ 

48 "name": "thename", 

49 "type": "x509_pem", 

50 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", 

51 "signed_by": "whosigned", 

52 }, self.elem.to_dict()) 

53 

54 def test_parse_identity(self): 

55 source = TEST_CERTIFICATE["elements"][3] 

56 elem = HSMCertificateV2Element.from_dict(source) 

57 self.assertTrue(isinstance(elem, HSMCertificateV2ElementX509)) 

58 self.assertEqual(source, elem.to_dict()) 

59 

60 def test_from_dict_invalid_message(self): 

61 with self.assertRaises(ValueError) as e: 

62 HSMCertificateV2Element.from_dict({ 

63 "name": "quoting_enclave", 

64 "type": "x509_pem", 

65 "message": "not-base-64", 

66 "signed_by": "platform_ca" 

67 }) 

68 self.assertIn("Invalid message", str(e.exception)) 

69 

70 def test_get_value_notimplemented(self): 

71 with self.assertRaises(NotImplementedError): 

72 self.elem.get_value() 

73 

74 def test_from_pem(self): 

75 self.assertEqual({ 

76 "name": "thename", 

77 "type": "x509_pem", 

78 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", 

79 "signed_by": "whosigned", 

80 }, HSMCertificateV2ElementX509.from_pem(""" 

81 -----BEGIN CERTIFICATE----- 

82 dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl 

83 -----END CERTIFICATE----- 

84 """, "thename", "whosigned").to_dict()) 

85 

86 @patch("admin.certificate_v2.Path") 

87 @patch("admin.certificate_v2.HSMCertificateV2ElementX509.from_pem") 

88 def test_from_pemfile(self, from_pem, Path): 

89 Path.return_value.read_text.return_value = "the pem contents" 

90 from_pem.return_value = "the instance" 

91 self.assertEqual("the instance", 

92 HSMCertificateV2ElementX509.from_pemfile("a-file.pem", 

93 "the name", 

94 "who signed")) 

95 Path.assert_called_with("a-file.pem") 

96 from_pem.assert_called_with("the pem contents", 

97 "the name", 

98 "who signed") 

99 

100 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

101 def test_certificate(self, load_pem_x509_certificate): 

102 load_pem_x509_certificate.return_value = "mock-certificate" 

103 

104 self.assertEqual("mock-certificate", self.elem.certificate) 

105 self.assertEqual("mock-certificate", self.elem.certificate) 

106 

107 load_pem_x509_certificate.assert_called_with( 

108 b"-----BEGIN CERTIFICATE-----" 

109 b"dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl" 

110 b"-----END CERTIFICATE-----" 

111 ) 

112 self.assertEqual(1, load_pem_x509_certificate.call_count) 

113 

114 def test_is_root_of_trust_no(self): 

115 self.assertFalse(self.elem.is_root_of_trust) 

116 

117 def test_is_root_of_trust_yes(self): 

118 self.elem = HSMCertificateV2ElementX509({ 

119 "name": "selfsigner", 

120 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", 

121 "signed_by": "selfsigner", 

122 }) 

123 self.assertTrue(self.elem.is_root_of_trust) 

124 

125 def setup_pubkey_mocks(self, load_pem_x509_certificate, VerifyingKey): 

126 self.pubkey = Mock() 

127 self.pubkey.curve = SECP256R1() 

128 self.pubkey.public_bytes.return_value = "the-public-bytes" 

129 self.cert = Mock() 

130 self.cert.public_key.return_value = self.pubkey 

131 load_pem_x509_certificate.return_value = self.cert 

132 VerifyingKey.from_string.return_value = "the-expected-pubkey" 

133 

134 @patch("admin.certificate_v2.ecdsa.VerifyingKey") 

135 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

136 def test_get_pubkey_ok(self, load_pem_x509_certificate, VerifyingKey): 

137 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey) 

138 

139 self.assertEqual("the-expected-pubkey", self.elem.get_pubkey()) 

140 self.pubkey.public_bytes.assert_called_with( 

141 Encoding.X962, PublicFormat.CompressedPoint) 

142 VerifyingKey.from_string.assert_called_with("the-public-bytes", NIST256p) 

143 

144 @patch("admin.certificate_v2.ecdsa.VerifyingKey") 

145 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

146 def test_get_pubkey_err_load_cert(self, load_pem_x509_certificate, VerifyingKey): 

147 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey) 

148 load_pem_x509_certificate.side_effect = Exception("blah blah") 

149 

150 with self.assertRaises(ValueError) as e: 

151 self.elem.get_pubkey() 

152 self.assertIn("gathering public key", str(e.exception)) 

153 self.assertIn("blah blah", str(e.exception)) 

154 self.pubkey.public_bytes.assert_not_called() 

155 VerifyingKey.from_string.assert_not_called() 

156 

157 @patch("admin.certificate_v2.ecdsa.VerifyingKey") 

158 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

159 def test_get_pubkey_err_get_pub(self, load_pem_x509_certificate, VerifyingKey): 

160 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey) 

161 self.cert.public_key.side_effect = Exception("blah blah") 

162 

163 with self.assertRaises(ValueError) as e: 

164 self.elem.get_pubkey() 

165 self.assertIn("gathering public key", str(e.exception)) 

166 self.assertIn("blah blah", str(e.exception)) 

167 self.pubkey.public_bytes.assert_not_called() 

168 VerifyingKey.from_string.assert_not_called() 

169 

170 @patch("admin.certificate_v2.ecdsa.VerifyingKey") 

171 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

172 def test_get_pubkey_err_pub_notnistp256(self, load_pem_x509_certificate, 

173 VerifyingKey): 

174 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey) 

175 self.pubkey.curve = "somethingelse" 

176 

177 with self.assertRaises(ValueError) as e: 

178 self.elem.get_pubkey() 

179 self.assertIn("gathering public key", str(e.exception)) 

180 self.assertIn("NIST P-256", str(e.exception)) 

181 self.pubkey.public_bytes.assert_not_called() 

182 VerifyingKey.from_string.assert_not_called() 

183 

184 @patch("admin.certificate_v2.ecdsa.VerifyingKey") 

185 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

186 def test_get_pubkey_err_public_bytes(self, load_pem_x509_certificate, VerifyingKey): 

187 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey) 

188 self.pubkey.public_bytes.side_effect = Exception("blah blah") 

189 

190 with self.assertRaises(ValueError) as e: 

191 self.elem.get_pubkey() 

192 self.assertIn("gathering public key", str(e.exception)) 

193 self.assertIn("blah blah", str(e.exception)) 

194 self.pubkey.public_bytes.assert_called_with( 

195 Encoding.X962, PublicFormat.CompressedPoint) 

196 VerifyingKey.from_string.assert_not_called() 

197 

198 @patch("admin.certificate_v2.ecdsa.VerifyingKey") 

199 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

200 def test_get_pubkey_err_ecdsafromstring(self, load_pem_x509_certificate, 

201 VerifyingKey): 

202 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey) 

203 VerifyingKey.from_string.side_effect = Exception("blah blah") 

204 

205 with self.assertRaises(ValueError) as e: 

206 self.elem.get_pubkey() 

207 self.assertIn("gathering public key", str(e.exception)) 

208 self.assertIn("blah blah", str(e.exception)) 

209 self.pubkey.public_bytes.assert_called_with( 

210 Encoding.X962, PublicFormat.CompressedPoint) 

211 VerifyingKey.from_string.assert_called_with("the-public-bytes", NIST256p) 

212 

213 def setup_is_valid_mocks(self, load_pem_x509_certificate): 

214 self.certifier = HSMCertificateV2ElementX509({ 

215 "name": "mock-certifier", 

216 "signed_by": "someone-else", 

217 "message": "Y2VydGlmaWVy" 

218 }) 

219 

220 self.mock_certifier = Mock() 

221 self.mock_elem = Mock() 

222 

223 self.mock_x509_validator = Mock() 

224 HSMCertificateV2ElementX509.set_certificate_validator(self.mock_x509_validator) 

225 

226 def load_mock(data): 

227 if b"Y2VydGlmaWVy" in data: 

228 return self.mock_certifier 

229 return self.mock_elem 

230 

231 load_pem_x509_certificate.side_effect = load_mock 

232 

233 @patch("admin.certificate_v2.info") 

234 @patch("admin.certificate_v2.datetime") 

235 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

236 def test_is_valid_yes(self, load_pem_x509_certificate, datetime, info): 

237 self.setup_is_valid_mocks(load_pem_x509_certificate) 

238 

239 self.mock_x509_validator.validate.return_value = { 

240 "valid": True, 

241 "warnings": ["one warning", "another warning"], 

242 } 

243 datetime.now.return_value = "this-is-now" 

244 

245 self.assertTrue(self.elem.is_valid(self.certifier)) 

246 self.mock_x509_validator.validate.assert_called_with( 

247 self.mock_elem, self.mock_certifier, "this-is-now", check_crl=True 

248 ) 

249 

250 @patch("admin.certificate_v2.info") 

251 @patch("admin.certificate_v2.datetime") 

252 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

253 def test_is_valid_no(self, load_pem_x509_certificate, datetime, info): 

254 self.setup_is_valid_mocks(load_pem_x509_certificate) 

255 

256 self.mock_x509_validator.validate.return_value = { 

257 "valid": False, 

258 "reason": "something happened", 

259 } 

260 datetime.now.return_value = "this-is-now" 

261 

262 self.assertFalse(self.elem.is_valid(self.certifier)) 

263 self.mock_x509_validator.validate.assert_called_with( 

264 self.mock_elem, self.mock_certifier, "this-is-now", check_crl=True 

265 ) 

266 

267 @patch("admin.certificate_v2.info") 

268 @patch("admin.certificate_v2.datetime") 

269 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

270 def test_is_valid_with_rot(self, load_pem_x509_certificate, datetime, info): 

271 self.elem = HSMCertificateV2ElementX509({ 

272 "name": "selfsigner", 

273 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", 

274 "signed_by": "selfsigner", 

275 }) 

276 self.setup_is_valid_mocks(load_pem_x509_certificate) 

277 

278 self.mock_x509_validator.validate.return_value = { 

279 "valid": True, 

280 "warnings": [], 

281 } 

282 datetime.now.return_value = "this-is-now" 

283 

284 self.assertTrue(self.elem.is_valid(self.certifier)) 

285 self.mock_x509_validator.validate.assert_called_with( 

286 self.mock_elem, self.mock_certifier, "this-is-now", check_crl=False 

287 ) 

288 

289 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

290 def test_is_valid_invalid_certifier(self, load_pem_x509_certificate): 

291 self.setup_is_valid_mocks(load_pem_x509_certificate) 

292 

293 with self.assertRaises(RuntimeError) as e: 

294 self.elem.is_valid("not-a-valid-certifier") 

295 

296 self.assertIn("Invalid certifier", str(e.exception)) 

297 self.mock_x509_validator.validate.assert_not_called() 

298 

299 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

300 def test_is_valid_cert_validator_not_set(self, load_pem_x509_certificate): 

301 self.setup_is_valid_mocks(load_pem_x509_certificate) 

302 HSMCertificateV2ElementX509.set_certificate_validator(None) 

303 

304 with self.assertRaises(RuntimeError) as e: 

305 self.elem.is_valid(self.certifier) 

306 

307 self.assertIn("Certificate validator not set", str(e.exception)) 

308 self.mock_x509_validator.validate.assert_not_called() 

309 

310 @patch("admin.certificate_v2.x509.load_pem_x509_certificate") 

311 def test_get_collateral_when_set(self, load_pem_x509_certificate): 

312 load_pem_x509_certificate.return_value = "mock-certificate" 

313 collateral_getter = Mock() 

314 collateral_getter.return_value = "the-collateral" 

315 HSMCertificateV2ElementX509.set_collateral_getter(collateral_getter) 

316 

317 self.assertEqual("the-collateral", self.elem.get_collateral()) 

318 

319 collateral_getter.assert_called_with("mock-certificate") 

320 

321 def test_get_collateral_when_not_set(self): 

322 with self.assertRaises(RuntimeError) as e: 

323 self.elem.get_collateral() 

324 self.assertIn("not set", str(e.exception))