Coverage for tests/admin/test_certificate_v1.py: 100%
129 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.
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': {
276 "valid": True,
277 "value": att_pubkey,
278 "tweak": None,
279 "collateral": {}
280 },
281 'device': {
282 "valid": True,
283 "value": device_pubkey,
284 "tweak": None,
285 "collateral": {}
286 },
287 }, cert.validate_and_get_values(root_of_trust))
289 def test_validate_and_get_values_invalid_element(self):
290 root_privkey = ec.PrivateKey()
291 root_pubkey = root_privkey.pubkey.serialize(compressed=False).hex()
292 root_of_trust = HSMCertificateRoot(root_pubkey)
293 device_privkey = ec.PrivateKey()
294 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
295 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex()
297 att_msg = 'ff' + att_pubkey
298 att_sig = 'aa' * 65
300 device_msg = os.urandom(16).hex() + device_pubkey
301 device_sig = root_privkey.ecdsa_serialize(
302 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex()
304 cert = HSMCertificate({
305 "version": 1,
306 "targets": ["attestation", "device"],
307 "elements": [
308 {
309 "name": "attestation",
310 "message": att_msg,
311 "signature": att_sig,
312 "signed_by": "device"
313 },
314 {
315 "name": "device",
316 "message": device_msg,
317 "signature": device_sig,
318 "signed_by": "root"
319 }]
320 })
322 self.assertEqual({
323 'attestation': {
324 "valid": False,
325 "failed_element": "attestation"
326 },
327 'device': {
328 "valid": True,
329 "value": device_pubkey,
330 "tweak": None,
331 "collateral": {}
332 },
333 }, cert.validate_and_get_values(root_of_trust))
335 def test_validate_and_get_values_invalid_elements(self):
336 att_privkey = ec.PrivateKey()
337 att_msg = os.urandom(66).hex()
338 att_sig = 'aa' * 65
340 device_privkey = ec.PrivateKey()
341 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
342 device_msg = os.urandom(16).hex() + \
343 att_privkey.pubkey.serialize(compressed=False).hex()
344 device_sig = 'bb' * 65
346 cert = HSMCertificate({
347 "version": 1,
348 "targets": ["attestation", "device"],
349 "elements": [
350 {
351 "name": "attestation",
352 "message": att_msg,
353 "signature": att_sig,
354 "signed_by": "device"
355 },
356 {
357 "name": "device",
358 "message": device_msg,
359 "signature": device_sig,
360 "signed_by": "root"
361 }]
362 })
364 self.assertEqual({
365 'attestation': {
366 "valid": False,
367 "failed_element": "device"
368 },
369 'device': {
370 "valid": False,
371 "failed_element": "device"
372 },
373 }, cert.validate_and_get_values(device_pubkey))
375 def test_add_element_ok(self):
376 cert = HSMCertificate()
377 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
379 cert.add_element(HSMCertificateElement({
380 "name": "device",
381 "message": 'cc',
382 "signature": 'dd',
383 "signed_by": "root"
384 }))
385 self.assertEqual({'version': 1, 'targets': [], 'elements': [
386 {
387 "name": "device",
388 "message": 'cc',
389 "signature": 'dd',
390 "signed_by": "root"
391 }
392 ]}, cert.to_dict())
394 def test_add_element_invalid_element(self):
395 cert = HSMCertificate()
396 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
397 with self.assertRaises(ValueError):
398 cert.add_element('not-an-element')
399 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
401 def test_add_target_ok(self):
402 cert = HSMCertificate({
403 "version": 1,
404 "targets": [],
405 "elements": [
406 {
407 "name": "attestation",
408 "message": 'aa',
409 "signature": 'bb',
410 "signed_by": "device"
411 },
412 {
413 "name": "device",
414 "message": 'cc',
415 "signature": 'dd',
416 "signed_by": "root"
417 }]
418 })
419 cert.add_target('attestation')
420 cert.add_target('device')
421 self.assertEqual({
422 "version": 1,
423 "targets": ["attestation", "device"],
424 "elements": [
425 {
426 "name": "attestation",
427 "message": 'aa',
428 "signature": 'bb',
429 "signed_by": "device"
430 },
431 {
432 "name": "device",
433 "message": 'cc',
434 "signature": 'dd',
435 "signed_by": "root"
436 }]
437 }, cert.to_dict())
439 def test_add_target_not_in_elements(self):
440 cert = HSMCertificate({
441 "version": 1,
442 "targets": [],
443 "elements": [
444 {
445 "name": "attestation",
446 "message": 'aa',
447 "signature": 'bb',
448 "signed_by": "device"
449 },
450 {
451 "name": "device",
452 "message": 'cc',
453 "signature": 'dd',
454 "signed_by": "root"
455 }]
456 })
457 cert.add_target('attestation')
458 with self.assertRaises(ValueError):
459 cert.add_target('ui')
460 self.assertEqual({
461 "version": 1,
462 "targets": ["attestation"],
463 "elements": [
464 {
465 "name": "attestation",
466 "message": 'aa',
467 "signature": 'bb',
468 "signed_by": "device"
469 },
470 {
471 "name": "device",
472 "message": 'cc',
473 "signature": 'dd',
474 "signed_by": "root"
475 }]
476 }, cert.to_dict())
478 def test_clear_targets(self):
479 cert = HSMCertificate({
480 "version": 1,
481 "targets": ["attestation", "device"],
482 "elements": [
483 {
484 "name": "attestation",
485 "message": 'aa',
486 "signature": 'bb',
487 "signed_by": "device"
488 },
489 {
490 "name": "device",
491 "message": 'cc',
492 "signature": 'dd',
493 "signed_by": "root"
494 }]
495 })
496 cert.clear_targets()
497 self.assertEqual({
498 "version": 1,
499 "targets": [],
500 "elements": [
501 {
502 "name": "attestation",
503 "message": 'aa',
504 "signature": 'bb',
505 "signed_by": "device"
506 },
507 {
508 "name": "device",
509 "message": 'cc',
510 "signature": 'dd',
511 "signed_by": "root"
512 }]
513 }, cert.to_dict())
515 def test_save_to_jsonfile_ok(self):
516 cert = HSMCertificate({
517 "version": 1,
518 "targets": ["attestation", "device"],
519 "elements": [
520 {
521 "name": "attestation",
522 "message": 'aa',
523 "signature": 'bb',
524 "signed_by": "device"
525 },
526 {
527 "name": "device",
528 "message": 'cc',
529 "signature": 'dd',
530 "signed_by": "root"
531 }]
532 })
533 with patch('builtins.open', mock_open()) as file_mock:
534 cert.save_to_jsonfile('file-path')
535 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list)
536 self.assertEqual([call('{\n "version": 1,\n'
537 ' "targets": [\n'
538 ' "attestation",\n'
539 ' "device"\n ],\n'
540 ' "elements": [\n'
541 ' {\n'
542 ' "name": "attestation",\n'
543 ' "message": "aa",\n'
544 ' "signature": "bb",\n'
545 ' "signed_by": "device"\n'
546 ' },\n'
547 ' {\n'
548 ' "name": "device",\n'
549 ' "message": "cc",\n'
550 ' "signature": "dd",\n'
551 ' "signed_by": "root"\n'
552 ' }\n'
553 ' ]\n'
554 '}\n')], file_mock.return_value.write.call_args_list)
556 def test_save_to_jsonfile_write_error(self):
557 cert = HSMCertificate({
558 "version": 1,
559 "targets": ["attestation", "device"],
560 "elements": [
561 {
562 "name": "attestation",
563 "message": 'aa',
564 "signature": 'bb',
565 "signed_by": "device"
566 },
567 {
568 "name": "device",
569 "message": 'cc',
570 "signature": 'dd',
571 "signed_by": "root"
572 }]
573 })
574 with patch('builtins.open', mock_open()) as file_mock:
575 file_mock.side_effect = Exception()
576 with self.assertRaises(Exception):
577 cert.save_to_jsonfile('file-path')
578 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list)
579 self.assertFalse(file_mock.return_value.write.called)
581 def test_from_jsonfile_ok(self):
582 cert_dict = {
583 "version": 1,
584 "targets": ["attestation", "device"],
585 "elements": [
586 {
587 "name": "attestation",
588 "message": 'aa',
589 "signature": 'bb',
590 "signed_by": "device"
591 },
592 {
593 "name": "device",
594 "message": 'cc',
595 "signature": 'dd',
596 "signed_by": "root"
597 }]
598 }
599 with patch('builtins.open', mock_open(read_data=json.dumps(cert_dict))) as file:
600 certificate = HSMCertificate.from_jsonfile('file-path')
601 self.assertEqual([call('file-path', 'r')], file.call_args_list)
602 self.assertEqual(cert_dict, certificate.to_dict())
604 def test_from_jsonfile_error(self):
605 with patch('builtins.open', mock_open(read_data='invalid-data')) as file:
606 with self.assertRaises(ValueError):
607 HSMCertificate.from_jsonfile('file-path')
608 self.assertEqual([call('file-path', 'r')], file.call_args_list)