Coverage for tests/admin/test_certificate_v1.py: 100%
129 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.
23import json
24import os
25import secp256k1 as ec
27from unittest import TestCase
28from unittest.mock import call, patch, mock_open
29from admin.certificate import HSMCertificate, HSMCertificateRoot, HSMCertificateElement
32class TestHSMCertificate(TestCase):
33 def test_create_valid_certificate_ok(self):
34 cert = HSMCertificate({
35 "version": 1,
36 "targets": ["attestation", "device"],
37 "elements": [
38 {
39 "name": "attestation",
40 "message": 'aa',
41 "signature": 'bb',
42 "signed_by": "device"
43 },
44 {
45 "name": "device",
46 "message": 'cc',
47 "signature": 'dd',
48 "signed_by": "root"
49 }]
50 })
51 self.assertEqual({
52 "version": 1,
53 "targets": ["attestation", "device"],
54 "elements": [
55 {
56 "name": "attestation",
57 "message": 'aa',
58 "signature": 'bb',
59 "signed_by": "device"
60 },
61 {
62 "name": "device",
63 "message": 'cc',
64 "signature": 'dd',
65 "signed_by": "root"
66 }]
67 }, cert.to_dict())
69 def test_create_empty_certificate_ok(self):
70 cert = HSMCertificate()
71 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
73 def test_create_certificate_invalid_version(self):
74 with self.assertRaises(ValueError):
75 HSMCertificate({
76 "version": 99,
77 "targets": ["attestation", "device"],
78 "elements": [
79 {
80 "name": "attestation",
81 "message": 'aa',
82 "signature": 'bb',
83 "signed_by": "device"
84 },
85 {
86 "name": "device",
87 "message": 'cc',
88 "signature": 'dd',
89 "signed_by": "root"
90 }]
91 })
93 def test_create_certificate_no_version(self):
94 with self.assertRaises(ValueError):
95 HSMCertificate({
96 "targets": ["attestation", "device"],
97 "elements": [
98 {
99 "name": "attestation",
100 "message": 'aa',
101 "signature": 'bb',
102 "signed_by": "device"
103 },
104 {
105 "name": "device",
106 "message": 'cc',
107 "signature": 'dd',
108 "signed_by": "root"
109 }]
110 })
112 def test_create_certificate_missing_targets(self):
113 with self.assertRaises(ValueError):
114 HSMCertificate({
115 "version": 1,
116 "elements": [
117 {
118 "name": "attestation",
119 "message": 'aa',
120 "signature": 'bb',
121 "signed_by": "device"
122 },
123 {
124 "name": "device",
125 "message": 'cc',
126 "signature": 'dd',
127 "signed_by": "root"
128 }]
129 })
131 def test_create_certificate_invalid_targets(self):
132 with self.assertRaises(ValueError):
133 HSMCertificate({
134 "version": 1,
135 "targets": "invalid-targets",
136 "elements": [
137 {
138 "name": "attestation",
139 "message": 'aa',
140 "signature": 'bb',
141 "signed_by": "device"
142 },
143 {
144 "name": "device",
145 "message": 'cc',
146 "signature": 'dd',
147 "signed_by": "root"
148 }]
149 })
151 def test_create_certificate_missing_elements(self):
152 with self.assertRaises(ValueError):
153 HSMCertificate({
154 "version": 1,
155 "targets": ["attestation", "device"]
156 })
158 @patch('admin.certificate_v1.HSMCertificate.ELEMENT_FACTORY')
159 def test_create_certificate_invalid_element(self, certElementMock):
160 certElementMock.side_effect = ValueError()
161 with self.assertRaises(ValueError):
162 HSMCertificate({
163 "version": 1,
164 "targets": ["attestation", "device"],
165 "elements": [
166 {
167 "name": "attestation",
168 "message": 'aa',
169 "signature": 'bb',
170 "signed_by": "device"
171 },
172 {
173 "name": "device",
174 "message": 'cc',
175 "signature": 'dd',
176 "signed_by": "root"
177 }]
178 })
180 def test_create_certificate_target_not_in_elements(self):
181 with self.assertRaises(ValueError):
182 HSMCertificate({
183 "version": 1,
184 "targets": ["attestation", "device", "ui"],
185 "elements": [
186 {
187 "name": "attestation",
188 "message": 'aa',
189 "signature": 'bb',
190 "signed_by": "device"
191 },
192 {
193 "name": "device",
194 "message": 'cc',
195 "signature": 'dd',
196 "signed_by": "root"
197 }]
198 })
200 def test_create_certificate_elements_without_path_to_root(self):
201 with self.assertRaises(ValueError):
202 HSMCertificate({
203 "version": 1,
204 "targets": ["attestation", "device"],
205 "elements": [
206 {
207 "name": "attestation",
208 "message": 'aa',
209 "signature": 'bb',
210 "signed_by": "attestation"
211 },
212 {
213 "name": "device",
214 "message": 'cc',
215 "signature": 'dd',
216 "signed_by": "root"
217 }]
218 })
220 def test_create_certificate_signer_not_in_elements(self):
221 with self.assertRaises(ValueError):
222 HSMCertificate({
223 "version": 1,
224 "targets": ["attestation", "device"],
225 "elements": [
226 {
227 "name": "attestation",
228 "message": 'aa',
229 "signature": 'bb',
230 "signed_by": "signer"
231 },
232 {
233 "name": "device",
234 "message": 'cc',
235 "signature": 'dd',
236 "signed_by": "root"
237 }]
238 })
240 def test_validate_and_get_values_ok(self):
241 root_privkey = ec.PrivateKey()
242 root_pubkey = root_privkey.pubkey.serialize(compressed=False).hex()
243 root_of_trust = HSMCertificateRoot(root_pubkey)
244 device_privkey = ec.PrivateKey()
245 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
246 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex()
248 att_msg = 'ff' + att_pubkey
249 att_sig = device_privkey.ecdsa_serialize(
250 device_privkey.ecdsa_sign(bytes.fromhex(att_msg))).hex()
252 device_msg = os.urandom(16).hex() + device_pubkey
253 device_sig = root_privkey.ecdsa_serialize(
254 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex()
256 cert = HSMCertificate({
257 "version": 1,
258 "targets": ["attestation", "device"],
259 "elements": [
260 {
261 "name": "attestation",
262 "message": att_msg,
263 "signature": att_sig,
264 "signed_by": "device"
265 },
266 {
267 "name": "device",
268 "message": device_msg,
269 "signature": device_sig,
270 "signed_by": "root"
271 }]
272 })
274 self.assertEqual({
275 'attestation': (True, att_pubkey, None),
276 'device': (True, device_pubkey, None)
277 }, cert.validate_and_get_values(root_of_trust))
279 def test_validate_and_get_values_invalid_element(self):
280 root_privkey = ec.PrivateKey()
281 root_pubkey = root_privkey.pubkey.serialize(compressed=False).hex()
282 root_of_trust = HSMCertificateRoot(root_pubkey)
283 device_privkey = ec.PrivateKey()
284 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
285 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex()
287 att_msg = 'ff' + att_pubkey
288 att_sig = 'aa' * 65
290 device_msg = os.urandom(16).hex() + device_pubkey
291 device_sig = root_privkey.ecdsa_serialize(
292 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex()
294 cert = HSMCertificate({
295 "version": 1,
296 "targets": ["attestation", "device"],
297 "elements": [
298 {
299 "name": "attestation",
300 "message": att_msg,
301 "signature": att_sig,
302 "signed_by": "device"
303 },
304 {
305 "name": "device",
306 "message": device_msg,
307 "signature": device_sig,
308 "signed_by": "root"
309 }]
310 })
312 self.assertEqual({
313 'attestation': (False, 'attestation'),
314 'device': (True, device_pubkey, None)
315 }, cert.validate_and_get_values(root_of_trust))
317 def test_validate_and_get_values_invalid_elements(self):
318 att_privkey = ec.PrivateKey()
319 att_msg = os.urandom(66).hex()
320 att_sig = 'aa' * 65
322 device_privkey = ec.PrivateKey()
323 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
324 device_msg = os.urandom(16).hex() + \
325 att_privkey.pubkey.serialize(compressed=False).hex()
326 device_sig = 'bb' * 65
328 cert = HSMCertificate({
329 "version": 1,
330 "targets": ["attestation", "device"],
331 "elements": [
332 {
333 "name": "attestation",
334 "message": att_msg,
335 "signature": att_sig,
336 "signed_by": "device"
337 },
338 {
339 "name": "device",
340 "message": device_msg,
341 "signature": device_sig,
342 "signed_by": "root"
343 }]
344 })
346 self.assertEqual({
347 'attestation': (False, 'device'),
348 'device': (False, 'device')
349 }, cert.validate_and_get_values(device_pubkey))
351 def test_add_element_ok(self):
352 cert = HSMCertificate()
353 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
355 cert.add_element(HSMCertificateElement({
356 "name": "device",
357 "message": 'cc',
358 "signature": 'dd',
359 "signed_by": "root"
360 }))
361 self.assertEqual({'version': 1, 'targets': [], 'elements': [
362 {
363 "name": "device",
364 "message": 'cc',
365 "signature": 'dd',
366 "signed_by": "root"
367 }
368 ]}, cert.to_dict())
370 def test_add_element_invalid_element(self):
371 cert = HSMCertificate()
372 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
373 with self.assertRaises(ValueError):
374 cert.add_element('not-an-element')
375 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
377 def test_add_target_ok(self):
378 cert = HSMCertificate({
379 "version": 1,
380 "targets": [],
381 "elements": [
382 {
383 "name": "attestation",
384 "message": 'aa',
385 "signature": 'bb',
386 "signed_by": "device"
387 },
388 {
389 "name": "device",
390 "message": 'cc',
391 "signature": 'dd',
392 "signed_by": "root"
393 }]
394 })
395 cert.add_target('attestation')
396 cert.add_target('device')
397 self.assertEqual({
398 "version": 1,
399 "targets": ["attestation", "device"],
400 "elements": [
401 {
402 "name": "attestation",
403 "message": 'aa',
404 "signature": 'bb',
405 "signed_by": "device"
406 },
407 {
408 "name": "device",
409 "message": 'cc',
410 "signature": 'dd',
411 "signed_by": "root"
412 }]
413 }, cert.to_dict())
415 def test_add_target_not_in_elements(self):
416 cert = HSMCertificate({
417 "version": 1,
418 "targets": [],
419 "elements": [
420 {
421 "name": "attestation",
422 "message": 'aa',
423 "signature": 'bb',
424 "signed_by": "device"
425 },
426 {
427 "name": "device",
428 "message": 'cc',
429 "signature": 'dd',
430 "signed_by": "root"
431 }]
432 })
433 cert.add_target('attestation')
434 with self.assertRaises(ValueError):
435 cert.add_target('ui')
436 self.assertEqual({
437 "version": 1,
438 "targets": ["attestation"],
439 "elements": [
440 {
441 "name": "attestation",
442 "message": 'aa',
443 "signature": 'bb',
444 "signed_by": "device"
445 },
446 {
447 "name": "device",
448 "message": 'cc',
449 "signature": 'dd',
450 "signed_by": "root"
451 }]
452 }, cert.to_dict())
454 def test_clear_targets(self):
455 cert = HSMCertificate({
456 "version": 1,
457 "targets": ["attestation", "device"],
458 "elements": [
459 {
460 "name": "attestation",
461 "message": 'aa',
462 "signature": 'bb',
463 "signed_by": "device"
464 },
465 {
466 "name": "device",
467 "message": 'cc',
468 "signature": 'dd',
469 "signed_by": "root"
470 }]
471 })
472 cert.clear_targets()
473 self.assertEqual({
474 "version": 1,
475 "targets": [],
476 "elements": [
477 {
478 "name": "attestation",
479 "message": 'aa',
480 "signature": 'bb',
481 "signed_by": "device"
482 },
483 {
484 "name": "device",
485 "message": 'cc',
486 "signature": 'dd',
487 "signed_by": "root"
488 }]
489 }, cert.to_dict())
491 def test_save_to_jsonfile_ok(self):
492 cert = HSMCertificate({
493 "version": 1,
494 "targets": ["attestation", "device"],
495 "elements": [
496 {
497 "name": "attestation",
498 "message": 'aa',
499 "signature": 'bb',
500 "signed_by": "device"
501 },
502 {
503 "name": "device",
504 "message": 'cc',
505 "signature": 'dd',
506 "signed_by": "root"
507 }]
508 })
509 with patch('builtins.open', mock_open()) as file_mock:
510 cert.save_to_jsonfile('file-path')
511 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list)
512 self.assertEqual([call('{\n "version": 1,\n'
513 ' "targets": [\n'
514 ' "attestation",\n'
515 ' "device"\n ],\n'
516 ' "elements": [\n'
517 ' {\n'
518 ' "name": "attestation",\n'
519 ' "message": "aa",\n'
520 ' "signature": "bb",\n'
521 ' "signed_by": "device"\n'
522 ' },\n'
523 ' {\n'
524 ' "name": "device",\n'
525 ' "message": "cc",\n'
526 ' "signature": "dd",\n'
527 ' "signed_by": "root"\n'
528 ' }\n'
529 ' ]\n'
530 '}\n')], file_mock.return_value.write.call_args_list)
532 def test_save_to_jsonfile_write_error(self):
533 cert = HSMCertificate({
534 "version": 1,
535 "targets": ["attestation", "device"],
536 "elements": [
537 {
538 "name": "attestation",
539 "message": 'aa',
540 "signature": 'bb',
541 "signed_by": "device"
542 },
543 {
544 "name": "device",
545 "message": 'cc',
546 "signature": 'dd',
547 "signed_by": "root"
548 }]
549 })
550 with patch('builtins.open', mock_open()) as file_mock:
551 file_mock.side_effect = Exception()
552 with self.assertRaises(Exception):
553 cert.save_to_jsonfile('file-path')
554 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list)
555 self.assertFalse(file_mock.return_value.write.called)
557 def test_from_jsonfile_ok(self):
558 cert_dict = {
559 "version": 1,
560 "targets": ["attestation", "device"],
561 "elements": [
562 {
563 "name": "attestation",
564 "message": 'aa',
565 "signature": 'bb',
566 "signed_by": "device"
567 },
568 {
569 "name": "device",
570 "message": 'cc',
571 "signature": 'dd',
572 "signed_by": "root"
573 }]
574 }
575 with patch('builtins.open', mock_open(read_data=json.dumps(cert_dict))) as file:
576 certificate = HSMCertificate.from_jsonfile('file-path')
577 self.assertEqual([call('file-path', 'r')], file.call_args_list)
578 self.assertEqual(cert_dict, certificate.to_dict())
580 def test_from_jsonfile_error(self):
581 with patch('builtins.open', mock_open(read_data='invalid-data')) as file:
582 with self.assertRaises(ValueError):
583 HSMCertificate.from_jsonfile('file-path')
584 self.assertEqual([call('file-path', 'r')], file.call_args_list)