Coverage for tests/admin/test_certificate.py: 100%
137 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 20:41 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 20:41 +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, HSMCertificateElement
32class TestCertificate(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.HSMCertificateElement')
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 device_privkey = ec.PrivateKey()
244 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
245 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex()
247 att_msg = 'ff' + att_pubkey
248 att_sig = device_privkey.ecdsa_serialize(
249 device_privkey.ecdsa_sign(bytes.fromhex(att_msg))).hex()
251 device_msg = os.urandom(16).hex() + device_pubkey
252 device_sig = root_privkey.ecdsa_serialize(
253 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex()
255 cert = HSMCertificate({
256 "version": 1,
257 "targets": ["attestation", "device"],
258 "elements": [
259 {
260 "name": "attestation",
261 "message": att_msg,
262 "signature": att_sig,
263 "signed_by": "device"
264 },
265 {
266 "name": "device",
267 "message": device_msg,
268 "signature": device_sig,
269 "signed_by": "root"
270 }]
271 })
273 self.assertEqual({
274 'attestation': (True, att_pubkey, None),
275 'device': (True, device_pubkey, None)
276 }, cert.validate_and_get_values(root_pubkey))
278 def test_create_and_get_values_invalid_pubkey(self):
279 root_privkey = ec.PrivateKey()
280 device_privkey = ec.PrivateKey()
281 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
282 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex()
284 att_msg = 'ff' + att_pubkey
285 att_sig = device_privkey.ecdsa_serialize(
286 device_privkey.ecdsa_sign(bytes.fromhex(att_msg))).hex()
288 device_msg = os.urandom(16).hex() + device_pubkey
289 device_sig = root_privkey.ecdsa_serialize(
290 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex()
292 cert = HSMCertificate({
293 "version": 1,
294 "targets": ["attestation", "device"],
295 "elements": [
296 {
297 "name": "attestation",
298 "message": att_msg,
299 "signature": att_sig,
300 "signed_by": "device"
301 },
302 {
303 "name": "device",
304 "message": device_msg,
305 "signature": device_sig,
306 "signed_by": "root"
307 }]
308 })
310 self.assertEqual({
311 'attestation': (False, 'root'),
312 'device': (False, 'root')
313 }, cert.validate_and_get_values('invalid-pubkey'))
315 def test_validate_and_get_values_invalid_element(self):
316 root_privkey = ec.PrivateKey()
317 root_pubkey = root_privkey.pubkey.serialize(compressed=False).hex()
318 device_privkey = ec.PrivateKey()
319 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
320 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex()
322 att_msg = 'ff' + att_pubkey
323 att_sig = 'aa' * 65
325 device_msg = os.urandom(16).hex() + device_pubkey
326 device_sig = root_privkey.ecdsa_serialize(
327 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex()
329 cert = HSMCertificate({
330 "version": 1,
331 "targets": ["attestation", "device"],
332 "elements": [
333 {
334 "name": "attestation",
335 "message": att_msg,
336 "signature": att_sig,
337 "signed_by": "device"
338 },
339 {
340 "name": "device",
341 "message": device_msg,
342 "signature": device_sig,
343 "signed_by": "root"
344 }]
345 })
347 self.assertEqual({
348 'attestation': (False, 'attestation'),
349 'device': (True, device_pubkey, None)
350 }, cert.validate_and_get_values(root_pubkey))
352 def test_validate_and_get_values_invalid_elements(self):
353 att_privkey = ec.PrivateKey()
354 att_msg = os.urandom(66).hex()
355 att_sig = 'aa' * 65
357 device_privkey = ec.PrivateKey()
358 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex()
359 device_msg = os.urandom(16).hex() + \
360 att_privkey.pubkey.serialize(compressed=False).hex()
361 device_sig = 'bb' * 65
363 cert = HSMCertificate({
364 "version": 1,
365 "targets": ["attestation", "device"],
366 "elements": [
367 {
368 "name": "attestation",
369 "message": att_msg,
370 "signature": att_sig,
371 "signed_by": "device"
372 },
373 {
374 "name": "device",
375 "message": device_msg,
376 "signature": device_sig,
377 "signed_by": "root"
378 }]
379 })
381 self.assertEqual({
382 'attestation': (False, 'device'),
383 'device': (False, 'device')
384 }, cert.validate_and_get_values(device_pubkey))
386 def test_add_element_ok(self):
387 cert = HSMCertificate()
388 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
390 cert.add_element(HSMCertificateElement({
391 "name": "device",
392 "message": 'cc',
393 "signature": 'dd',
394 "signed_by": "root"
395 }))
396 self.assertEqual({'version': 1, 'targets': [], 'elements': [
397 {
398 "name": "device",
399 "message": 'cc',
400 "signature": 'dd',
401 "signed_by": "root"
402 }
403 ]}, cert.to_dict())
405 def test_add_element_invalid_element(self):
406 cert = HSMCertificate()
407 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
408 with self.assertRaises(ValueError):
409 cert.add_element('not-an-element')
410 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict())
412 def test_add_target_ok(self):
413 cert = HSMCertificate({
414 "version": 1,
415 "targets": [],
416 "elements": [
417 {
418 "name": "attestation",
419 "message": 'aa',
420 "signature": 'bb',
421 "signed_by": "device"
422 },
423 {
424 "name": "device",
425 "message": 'cc',
426 "signature": 'dd',
427 "signed_by": "root"
428 }]
429 })
430 cert.add_target('attestation')
431 cert.add_target('device')
432 self.assertEqual({
433 "version": 1,
434 "targets": ["attestation", "device"],
435 "elements": [
436 {
437 "name": "attestation",
438 "message": 'aa',
439 "signature": 'bb',
440 "signed_by": "device"
441 },
442 {
443 "name": "device",
444 "message": 'cc',
445 "signature": 'dd',
446 "signed_by": "root"
447 }]
448 }, cert.to_dict())
450 def test_add_target_not_in_elements(self):
451 cert = HSMCertificate({
452 "version": 1,
453 "targets": [],
454 "elements": [
455 {
456 "name": "attestation",
457 "message": 'aa',
458 "signature": 'bb',
459 "signed_by": "device"
460 },
461 {
462 "name": "device",
463 "message": 'cc',
464 "signature": 'dd',
465 "signed_by": "root"
466 }]
467 })
468 cert.add_target('attestation')
469 with self.assertRaises(ValueError):
470 cert.add_target('ui')
471 self.assertEqual({
472 "version": 1,
473 "targets": ["attestation"],
474 "elements": [
475 {
476 "name": "attestation",
477 "message": 'aa',
478 "signature": 'bb',
479 "signed_by": "device"
480 },
481 {
482 "name": "device",
483 "message": 'cc',
484 "signature": 'dd',
485 "signed_by": "root"
486 }]
487 }, cert.to_dict())
489 def test_clear_targets(self):
490 cert = HSMCertificate({
491 "version": 1,
492 "targets": ["attestation", "device"],
493 "elements": [
494 {
495 "name": "attestation",
496 "message": 'aa',
497 "signature": 'bb',
498 "signed_by": "device"
499 },
500 {
501 "name": "device",
502 "message": 'cc',
503 "signature": 'dd',
504 "signed_by": "root"
505 }]
506 })
507 cert.clear_targets()
508 self.assertEqual({
509 "version": 1,
510 "targets": [],
511 "elements": [
512 {
513 "name": "attestation",
514 "message": 'aa',
515 "signature": 'bb',
516 "signed_by": "device"
517 },
518 {
519 "name": "device",
520 "message": 'cc',
521 "signature": 'dd',
522 "signed_by": "root"
523 }]
524 }, cert.to_dict())
526 def test_save_to_jsonfile_ok(self):
527 cert = HSMCertificate({
528 "version": 1,
529 "targets": ["attestation", "device"],
530 "elements": [
531 {
532 "name": "attestation",
533 "message": 'aa',
534 "signature": 'bb',
535 "signed_by": "device"
536 },
537 {
538 "name": "device",
539 "message": 'cc',
540 "signature": 'dd',
541 "signed_by": "root"
542 }]
543 })
544 with patch('builtins.open', mock_open()) as file_mock:
545 cert.save_to_jsonfile('file-path')
546 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list)
547 self.assertEqual([call('{\n "version": 1,\n'
548 ' "targets": [\n'
549 ' "attestation",\n'
550 ' "device"\n ],\n'
551 ' "elements": [\n'
552 ' {\n'
553 ' "name": "attestation",\n'
554 ' "message": "aa",\n'
555 ' "signature": "bb",\n'
556 ' "signed_by": "device"\n'
557 ' },\n'
558 ' {\n'
559 ' "name": "device",\n'
560 ' "message": "cc",\n'
561 ' "signature": "dd",\n'
562 ' "signed_by": "root"\n'
563 ' }\n'
564 ' ]\n'
565 '}\n')], file_mock.return_value.write.call_args_list)
567 def test_save_to_jsonfile_write_error(self):
568 cert = HSMCertificate({
569 "version": 1,
570 "targets": ["attestation", "device"],
571 "elements": [
572 {
573 "name": "attestation",
574 "message": 'aa',
575 "signature": 'bb',
576 "signed_by": "device"
577 },
578 {
579 "name": "device",
580 "message": 'cc',
581 "signature": 'dd',
582 "signed_by": "root"
583 }]
584 })
585 with patch('builtins.open', mock_open()) as file_mock:
586 file_mock.side_effect = Exception()
587 with self.assertRaises(Exception):
588 cert.save_to_jsonfile('file-path')
589 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list)
590 self.assertFalse(file_mock.return_value.write.called)
592 def test_from_jsonfile_ok(self):
593 cert_dict = {
594 "version": 1,
595 "targets": ["attestation", "device"],
596 "elements": [
597 {
598 "name": "attestation",
599 "message": 'aa',
600 "signature": 'bb',
601 "signed_by": "device"
602 },
603 {
604 "name": "device",
605 "message": 'cc',
606 "signature": 'dd',
607 "signed_by": "root"
608 }]
609 }
610 with patch('builtins.open', mock_open(read_data=json.dumps(cert_dict))) as file:
611 certificate = HSMCertificate.from_jsonfile('file-path')
612 self.assertEqual([call('file-path', 'r')], file.call_args_list)
613 self.assertEqual(cert_dict, certificate.to_dict())
615 def test_from_jsonfile_error(self):
616 with patch('builtins.open', mock_open(read_data='invalid-data')) as file:
617 with self.assertRaises(ValueError):
618 HSMCertificate.from_jsonfile('file-path')
619 self.assertEqual([call('file-path', 'r')], file.call_args_list)