Coverage for tests/admin/test_certificate_v2_element_x509.py: 100%
174 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +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 datetime import datetime, timedelta, UTC
24from unittest import TestCase
25from unittest.mock import Mock, patch
26from ecdsa import NIST256p
27from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding
28from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1
29from admin.certificate_v2 import HSMCertificateV2Element, \
30 HSMCertificateV2ElementX509
31from .test_certificate_v2_resources import TEST_CERTIFICATE
34class TestHSMCertificateV2ElementX509(TestCase):
35 def setUp(self):
36 self.elem = HSMCertificateV2ElementX509({
37 "name": "thename",
38 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl",
39 "signed_by": "whosigned",
40 })
42 def test_props(self):
43 self.assertEqual("thename", self.elem.name)
44 self.assertEqual("whosigned", self.elem.signed_by)
45 self.assertEqual("dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl", self.elem.message)
47 def test_dict_ok(self):
48 self.assertEqual({
49 "name": "thename",
50 "type": "x509_pem",
51 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl",
52 "signed_by": "whosigned",
53 }, self.elem.to_dict())
55 def test_parse_identity(self):
56 source = TEST_CERTIFICATE["elements"][3]
57 elem = HSMCertificateV2Element.from_dict(source)
58 self.assertTrue(isinstance(elem, HSMCertificateV2ElementX509))
59 self.assertEqual(source, elem.to_dict())
61 def test_from_dict_invalid_message(self):
62 with self.assertRaises(ValueError) as e:
63 HSMCertificateV2Element.from_dict({
64 "name": "quoting_enclave",
65 "type": "x509_pem",
66 "message": "not-base-64",
67 "signed_by": "platform_ca"
68 })
69 self.assertIn("Invalid message", str(e.exception))
71 def test_get_value_notimplemented(self):
72 with self.assertRaises(NotImplementedError):
73 self.elem.get_value()
75 def test_from_pem(self):
76 self.assertEqual({
77 "name": "thename",
78 "type": "x509_pem",
79 "message": "dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl",
80 "signed_by": "whosigned",
81 }, HSMCertificateV2ElementX509.from_pem("""
82 -----BEGIN CERTIFICATE-----
83 dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl
84 -----END CERTIFICATE-----
85 """, "thename", "whosigned").to_dict())
87 @patch("admin.certificate_v2.Path")
88 @patch("admin.certificate_v2.HSMCertificateV2ElementX509.from_pem")
89 def test_from_pemfile(self, from_pem, Path):
90 Path.return_value.read_text.return_value = "the pem contents"
91 from_pem.return_value = "the instance"
92 self.assertEqual("the instance",
93 HSMCertificateV2ElementX509.from_pemfile("a-file.pem",
94 "the name",
95 "who signed"))
96 Path.assert_called_with("a-file.pem")
97 from_pem.assert_called_with("the pem contents",
98 "the name",
99 "who signed")
101 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
102 def test_certificate(self, load_pem_x509_certificate):
103 load_pem_x509_certificate.return_value = "mock-certificate"
105 self.assertEqual("mock-certificate", self.elem.certificate)
106 self.assertEqual("mock-certificate", self.elem.certificate)
108 load_pem_x509_certificate.assert_called_with(
109 b"-----BEGIN CERTIFICATE-----"
110 b"dGhpcyBpcyBhbiBhc2NpaSBtZXNzYWdl"
111 b"-----END CERTIFICATE-----"
112 )
113 self.assertEqual(1, load_pem_x509_certificate.call_count)
115 def setup_pubkey_mocks(self, load_pem_x509_certificate, VerifyingKey):
116 self.pubkey = Mock()
117 self.pubkey.curve = SECP256R1()
118 self.pubkey.public_bytes.return_value = "the-public-bytes"
119 self.cert = Mock()
120 self.cert.public_key.return_value = self.pubkey
121 load_pem_x509_certificate.return_value = self.cert
122 VerifyingKey.from_string.return_value = "the-expected-pubkey"
124 @patch("admin.certificate_v2.ecdsa.VerifyingKey")
125 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
126 def test_get_pubkey_ok(self, load_pem_x509_certificate, VerifyingKey):
127 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey)
129 self.assertEqual("the-expected-pubkey", self.elem.get_pubkey())
130 self.pubkey.public_bytes.assert_called_with(
131 Encoding.X962, PublicFormat.CompressedPoint)
132 VerifyingKey.from_string.assert_called_with("the-public-bytes", NIST256p)
134 @patch("admin.certificate_v2.ecdsa.VerifyingKey")
135 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
136 def test_get_pubkey_err_load_cert(self, load_pem_x509_certificate, VerifyingKey):
137 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey)
138 load_pem_x509_certificate.side_effect = Exception("blah blah")
140 with self.assertRaises(ValueError) as e:
141 self.elem.get_pubkey()
142 self.assertIn("gathering public key", str(e.exception))
143 self.assertIn("blah blah", str(e.exception))
144 self.pubkey.public_bytes.assert_not_called()
145 VerifyingKey.from_string.assert_not_called()
147 @patch("admin.certificate_v2.ecdsa.VerifyingKey")
148 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
149 def test_get_pubkey_err_get_pub(self, load_pem_x509_certificate, VerifyingKey):
150 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey)
151 self.cert.public_key.side_effect = Exception("blah blah")
153 with self.assertRaises(ValueError) as e:
154 self.elem.get_pubkey()
155 self.assertIn("gathering public key", str(e.exception))
156 self.assertIn("blah blah", str(e.exception))
157 self.pubkey.public_bytes.assert_not_called()
158 VerifyingKey.from_string.assert_not_called()
160 @patch("admin.certificate_v2.ecdsa.VerifyingKey")
161 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
162 def test_get_pubkey_err_pub_notnistp256(self, load_pem_x509_certificate,
163 VerifyingKey):
164 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey)
165 self.pubkey.curve = "somethingelse"
167 with self.assertRaises(ValueError) as e:
168 self.elem.get_pubkey()
169 self.assertIn("gathering public key", str(e.exception))
170 self.assertIn("NIST P-256", str(e.exception))
171 self.pubkey.public_bytes.assert_not_called()
172 VerifyingKey.from_string.assert_not_called()
174 @patch("admin.certificate_v2.ecdsa.VerifyingKey")
175 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
176 def test_get_pubkey_err_public_bytes(self, load_pem_x509_certificate, VerifyingKey):
177 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey)
178 self.pubkey.public_bytes.side_effect = Exception("blah blah")
180 with self.assertRaises(ValueError) as e:
181 self.elem.get_pubkey()
182 self.assertIn("gathering public key", str(e.exception))
183 self.assertIn("blah blah", str(e.exception))
184 self.pubkey.public_bytes.assert_called_with(
185 Encoding.X962, PublicFormat.CompressedPoint)
186 VerifyingKey.from_string.assert_not_called()
188 @patch("admin.certificate_v2.ecdsa.VerifyingKey")
189 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
190 def test_get_pubkey_err_ecdsafromstring(self, load_pem_x509_certificate,
191 VerifyingKey):
192 self.setup_pubkey_mocks(load_pem_x509_certificate, VerifyingKey)
193 VerifyingKey.from_string.side_effect = Exception("blah blah")
195 with self.assertRaises(ValueError) as e:
196 self.elem.get_pubkey()
197 self.assertIn("gathering public key", str(e.exception))
198 self.assertIn("blah blah", str(e.exception))
199 self.pubkey.public_bytes.assert_called_with(
200 Encoding.X962, PublicFormat.CompressedPoint)
201 VerifyingKey.from_string.assert_called_with("the-public-bytes", NIST256p)
203 def setup_is_valid_mocks(self, load_pem_x509_certificate, ec):
204 self.certifier = HSMCertificateV2ElementX509({
205 "name": "mock-certifier",
206 "signed_by": "someone-else",
207 "message": "Y2VydGlmaWVy"
208 })
210 self.mock_certifier = Mock()
211 self.mock_elem = Mock()
213 def load_mock(data):
214 if b"Y2VydGlmaWVy" in data:
215 return self.mock_certifier
216 return self.mock_elem
218 load_pem_x509_certificate.side_effect = load_mock
220 self.now = datetime.now(UTC)
221 one_week = timedelta(weeks=1)
222 self.mock_elem.not_valid_before_utc = self.now - one_week
223 self.mock_elem.not_valid_after_utc = self.now + one_week
224 self.mock_certifier_pk = Mock()
225 self.mock_certifier.public_key.return_value = self.mock_certifier_pk
226 self.mock_elem.signature = "the-signature"
227 self.mock_elem.tbs_certificate_bytes = "the-fingerprint"
228 self.mock_elem.signature_hash_algorithm = "the-signature-hash-algo"
229 ec.ECDSA.return_value = "the-ecdsa-algo"
231 @patch("admin.certificate_v2.ec")
232 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
233 def test_is_valid_ok(self, load_pem_x509_certificate, ec):
234 self.setup_is_valid_mocks(load_pem_x509_certificate, ec)
236 self.assertTrue(self.elem.is_valid(self.certifier))
238 self.mock_certifier_pk.verify.assert_called_with(
239 "the-signature",
240 "the-fingerprint",
241 "the-ecdsa-algo"
242 )
243 ec.ECDSA.assert_called_with("the-signature-hash-algo")
245 @patch("admin.certificate_v2.ec")
246 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
247 def test_is_valid_before_in_future(self, load_pem_x509_certificate, ec):
248 self.setup_is_valid_mocks(load_pem_x509_certificate, ec)
249 self.mock_elem.not_valid_before_utc = self.now + \
250 timedelta(minutes=1)
252 self.assertFalse(self.elem.is_valid(self.certifier))
254 self.mock_certifier_pk.verify.assert_not_called()
255 ec.ECDSA.assert_not_called()
257 @patch("admin.certificate_v2.ec")
258 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
259 def test_is_valid_after_in_past(self, load_pem_x509_certificate, ec):
260 self.setup_is_valid_mocks(load_pem_x509_certificate, ec)
261 self.mock_elem.not_valid_after_utc = self.now - \
262 timedelta(minutes=1)
264 self.assertFalse(self.elem.is_valid(self.certifier))
266 self.mock_certifier_pk.verify.assert_not_called()
267 ec.ECDSA.assert_not_called()
269 @patch("admin.certificate_v2.ec")
270 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
271 def test_is_valid_signature_invalid(self, load_pem_x509_certificate, ec):
272 self.setup_is_valid_mocks(load_pem_x509_certificate, ec)
273 self.mock_certifier_pk.verify.side_effect = RuntimeError("wrong signature")
275 self.assertFalse(self.elem.is_valid(self.certifier))
277 self.mock_certifier_pk.verify.assert_called_with(
278 "the-signature",
279 "the-fingerprint",
280 "the-ecdsa-algo"
281 )
282 ec.ECDSA.assert_called_with("the-signature-hash-algo")
284 @patch("admin.certificate_v2.ec")
285 @patch("admin.certificate_v2.x509.load_pem_x509_certificate")
286 def test_is_valid_x509_error(self, load_pem_x509_certificate, ec):
287 self.setup_is_valid_mocks(load_pem_x509_certificate, ec)
288 load_pem_x509_certificate.side_effect = ValueError("a random error")
290 self.assertFalse(self.elem.is_valid(self.certifier))
292 self.mock_certifier_pk.verify.assert_not_called()
293 ec.ECDSA.assert_not_called()