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
« 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.
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
33class TestHSMCertificateV2ElementX509(TestCase):
34 def setUp(self):
35 self.elem = HSMCertificateV2ElementX509({
36 "name": "thename",
37 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl",
38 "signed_by": "whosigned",
39 })
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)
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())
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())
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))
70 def test_get_value_notimplemented(self):
71 with self.assertRaises(NotImplementedError):
72 self.elem.get_value()
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())
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")
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"
104 self.assertEqual("mock-certificate", self.elem.certificate)
105 self.assertEqual("mock-certificate", self.elem.certificate)
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)
114 def test_is_root_of_trust_no(self):
115 self.assertFalse(self.elem.is_root_of_trust)
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)
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"
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)
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)
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")
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()
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")
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()
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"
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()
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")
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()
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")
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)
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 })
220 self.mock_certifier = Mock()
221 self.mock_elem = Mock()
223 self.mock_x509_validator = Mock()
224 HSMCertificateV2ElementX509.set_certificate_validator(self.mock_x509_validator)
226 def load_mock(data):
227 if b"Y2VydGlmaWVy" in data:
228 return self.mock_certifier
229 return self.mock_elem
231 load_pem_x509_certificate.side_effect = load_mock
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)
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"
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 )
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)
256 self.mock_x509_validator.validate.return_value = {
257 "valid": False,
258 "reason": "something happened",
259 }
260 datetime.now.return_value = "this-is-now"
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 )
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)
278 self.mock_x509_validator.validate.return_value = {
279 "valid": True,
280 "warnings": [],
281 }
282 datetime.now.return_value = "this-is-now"
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 )
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)
293 with self.assertRaises(RuntimeError) as e:
294 self.elem.is_valid("not-a-valid-certifier")
296 self.assertIn("Invalid certifier", str(e.exception))
297 self.mock_x509_validator.validate.assert_not_called()
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)
304 with self.assertRaises(RuntimeError) as e:
305 self.elem.is_valid(self.certifier)
307 self.assertIn("Certificate validator not set", str(e.exception))
308 self.mock_x509_validator.validate.assert_not_called()
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)
317 self.assertEqual("the-collateral", self.elem.get_collateral())
319 collateral_getter.assert_called_with("mock-certificate")
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))