1#!/usr/bin/env python3 2# 3# Copyright (c) 2021 Nordic Semiconductor ASA 4# 5# SPDX-License-Identifier: Apache-2.0 6 7from unittest import TestCase, main, skipIf 8from subprocess import Popen, PIPE 9from re import sub 10from pathlib import Path 11from pprint import pprint 12from ecdsa import VerifyingKey 13from hashlib import sha256 14import cbor2 15from platform import python_version_tuple 16from sys import platform, exit 17from yaml import safe_load 18 19 20try: 21 import zcbor 22except ImportError: 23 print(""" 24The zcbor package must be installed to run these tests. 25During development, install with `python3 setup.py develop` to install in a way 26that picks up changes in the files without having to reinstall. 27""") 28 exit(1) 29 30 31p_root = Path(__file__).absolute().parents[2] 32p_tests = Path(p_root, 'tests') 33p_manifest12 = Path(p_tests, 'cases', 'manifest12.cddl') 34p_manifest14 = Path(p_tests, 'cases', 'manifest14.cddl') 35p_manifest16 = Path(p_tests, 'cases', 'manifest16.cddl') 36p_manifest20 = Path(p_tests, 'cases', 'manifest20.cddl') 37p_test_vectors12 = tuple(Path(p_tests, 'cases', f'manifest12_example{i}.cborhex') for i in range(6)) 38p_test_vectors14 = tuple(Path(p_tests, 'cases', f'manifest14_example{i}.cborhex') for i in range(6)) 39p_test_vectors16 = tuple(Path(p_tests, 'cases', f'manifest14_example{i}.cborhex') for i in range(6)) # Identical to manifest14. 40p_test_vectors20 = tuple(Path(p_tests, 'cases', f'manifest20_example{i}.cborhex') for i in range(6)) 41p_optional = Path(p_tests, 'cases', 'optional.cddl') 42p_corner_cases = Path(p_tests, 'cases', 'corner_cases.cddl') 43p_cose = Path(p_tests, 'cases', 'cose.cddl') 44p_manifest14_priv = Path(p_tests, 'cases', 'manifest14.priv') 45p_manifest14_pub = Path(p_tests, 'cases', 'manifest14.pub') 46p_map_bstr_cddl = Path(p_tests, 'cases', 'map_bstr.cddl') 47p_map_bstr_yaml = Path(p_tests, 'cases', 'map_bstr.yaml') 48p_yaml_compat_cddl = Path(p_tests, 'cases', 'yaml_compatibility.cddl') 49p_yaml_compat_yaml = Path(p_tests, 'cases', 'yaml_compatibility.yaml') 50p_README = Path(p_root, 'README.md') 51p_prelude = Path(p_root, 'zcbor', 'cddl', 'prelude.cddl') 52 53 54class TestManifest(TestCase): 55 """Class for testing examples against CDDL for various versions of the SUIT manifest spec.""" 56 def decode_file(self, data_path, *cddl_paths): 57 data = bytes.fromhex(data_path.read_text().replace("\n", "")) 58 self.decode_string(data, *cddl_paths) 59 60 def decode_string(self, data_string, *cddl_paths): 61 cddl_str = " ".join((Path(p).read_text() for p in cddl_paths)) 62 self.my_types = zcbor.DataTranslator.from_cddl(cddl_str, 16).my_types 63 cddl = self.my_types["SUIT_Envelope_Tagged"] 64 self.decoded = cddl.decode_str(data_string) 65 66 67class TestEx0Manifest12(TestManifest): 68 def __init__(self, *args, **kwargs): 69 super().__init__(*args, **kwargs) 70 self.decode_file(p_test_vectors12[0], p_manifest12) 71 72 def test_manifest_digest(self): 73 self.assertEqual( 74 bytes.fromhex("5c097ef64bf3bb9b494e71e1f2418eef8d466cc902f639a855ec9af3e9eddb99"), 75 self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr.suit_digest_bytes) 76 77 def test_signature(self): 78 self.assertEqual( 79 1, 80 self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.protected.uintint[0].uintint_key) 81 self.assertEqual( 82 -7, 83 self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.protected.uintint[0].uintint) 84 self.assertEqual( 85 bytes.fromhex("a19fd1f23b17beed321cece7423dfb48c457b8f1f6ac83577a3c10c6773f6f3a7902376b59540920b6c5f57bac5fc8543d8f5d3d974faa2e6d03daa534b443a7"), 86 self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.signature) 87 88 def test_validate_run(self): 89 self.assertEqual( 90 "suit_condition_image_match", 91 self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_validate[0].suit_validate.union[0].SUIT_Condition.union_choice) 92 self.assertEqual( 93 "suit_directive_run", 94 self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_run[0].suit_run.union[0].SUIT_Directive.union_choice) 95 96 def test_image_size(self): 97 self.assertEqual(34768, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[3].suit_parameter_image_size) 98 99 100class TestEx0InvManifest12(TestManifest): 101 def __init__(self, *args, **kwargs): 102 super().__init__(*args, **kwargs) 103 104 def test_duplicate_type(self): 105 with self.assertRaises(ValueError, msg="Duplicate CDDL type found"): 106 self.decode_file(p_test_vectors12[0], p_manifest12, p_manifest12) 107 108 109class TestEx1Manifest12(TestManifest): 110 def __init__(self, *args, **kwargs): 111 super().__init__(*args, **kwargs) 112 self.decode_file(p_test_vectors12[1], p_manifest12) 113 114 def test_components(self): 115 self.assertEqual( 116 [b'\x00'], 117 self.decoded.suit_manifest.suit_common.suit_components[0][0].bstr) 118 119 def test_uri(self): 120 self.assertEqual( 121 "http://example.com/file.bin", 122 self.decoded.suit_manifest.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[0].SUIT_Directive.suit_directive_set_parameters.map[0].suit_parameter_uri) 123 124 125class TestEx2Manifest12(TestManifest): 126 def __init__(self, *args, **kwargs): 127 super().__init__(*args, **kwargs) 128 self.decode_file(p_test_vectors12[2], p_manifest12) 129 130 def test_severed_uri(self): 131 self.assertEqual( 132 "http://example.com/very/long/path/to/file/file.bin", 133 self.decoded.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[0].SUIT_Directive.suit_directive_set_parameters.map[0].suit_parameter_uri) 134 135 def test_severed_text(self): 136 self.assertIn( 137 "Example 2", 138 self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Text_Keys.suit_text_manifest_description[0]) 139 self.assertEqual( 140 [b'\x00'], 141 self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier_key.bstr) 142 self.assertEqual( 143 "arm.com", 144 self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_vendor_domain[0]) 145 self.assertEqual( 146 "This component is a demonstration. The digest is a sample pattern, not a real one.", 147 self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_component_description[0]) 148 149 150class TestEx3Manifest12(TestManifest): 151 def __init__(self, *args, **kwargs): 152 super().__init__(*args, **kwargs) 153 self.decode_file(p_test_vectors12[3], p_manifest12) 154 155 def test_A_B_offset(self): 156 self.assertEqual( 157 33792, 158 self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[1].SUIT_Common_Commands.suit_directive_try_each.SUIT_Directive_Try_Each_Argument.SUIT_Command_Sequence_bstr[0].union[0].SUIT_Directive.suit_directive_override_parameters.map[0].suit_parameter_component_offset) 159 self.assertEqual( 160 541696, 161 self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[1].SUIT_Common_Commands.suit_directive_try_each.SUIT_Directive_Try_Each_Argument.SUIT_Command_Sequence_bstr[1].union[0].SUIT_Directive.suit_directive_override_parameters.map[0].suit_parameter_component_offset) 162 163 164class TestEx4Manifest12(TestManifest): 165 def __init__(self, *args, **kwargs): 166 super().__init__(*args, **kwargs) 167 self.decode_file(p_test_vectors12[4], p_manifest12) 168 169 def test_load_decompress(self): 170 self.assertEqual( 171 0, 172 self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_load[0].suit_load.union[1].SUIT_Directive.suit_directive_set_parameters.map[3].suit_parameter_source_component) 173 self.assertEqual( 174 "SUIT_Compression_Algorithm_zlib", 175 self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_load[0].suit_load.union[1].SUIT_Directive.suit_directive_set_parameters.map[2].suit_parameter_compression_info.suit_compression_algorithm) 176 177 178class TestEx5Manifest12(TestManifest): 179 def __init__(self, *args, **kwargs): 180 super().__init__(*args, **kwargs) 181 self.decode_file(p_test_vectors12[5], p_manifest12) 182 183 def test_two_image_match(self): 184 self.assertEqual( 185 "suit_condition_image_match", 186 self.decoded.suit_manifest.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[3].SUIT_Condition.union_choice) 187 self.assertEqual( 188 "suit_condition_image_match", 189 self.decoded.suit_manifest.SUIT_Severable_Manifest_Members.suit_install[0].suit_install.union[7].SUIT_Condition.union_choice) 190 191 192def dumps(obj): 193 return cbor2.dumps(obj, canonical=True) 194 195 196def loads(string): 197 return cbor2.loads(string) 198 199 200class TestEx0Manifest14(TestManifest): 201 def __init__(self, *args, **kwargs): 202 super().__init__(*args, **kwargs) 203 self.key = VerifyingKey.from_pem(p_manifest14_pub.read_text()) 204 205 def do_test_authentication(self): 206 self.assertEqual("COSE_Sign1_Tagged", self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].union_choice) 207 self.assertEqual(-7, self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.Headers.protected.header_map_bstr.Generic_Headers.uint1union[0].int) 208 209 manifest_signature = self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.signature 210 signature_header = self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.Headers.protected.header_map_bstr_bstr 211 manifest_suit_digest = self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr_bstr 212 213 sig_struct = dumps(["Signature1", signature_header, b'', manifest_suit_digest]) 214 215 self.key.verify(manifest_signature, sig_struct, hashfunc=sha256) 216 217 def test_auth_0(self): 218 self.decode_file(p_test_vectors14[0], p_manifest14, p_cose) 219 self.do_test_authentication() 220 221 def test_auth_1(self): 222 self.decode_file(p_test_vectors14[1], p_manifest14, p_cose) 223 self.do_test_authentication() 224 225 def test_auth_2(self): 226 self.decode_file(p_test_vectors14[2], p_manifest14, p_cose) 227 self.do_test_authentication() 228 229 def test_auth_3(self): 230 self.decode_file(p_test_vectors14[3], p_manifest14, p_cose) 231 self.do_test_authentication() 232 233 def test_auth_4(self): 234 self.decode_file(p_test_vectors14[4], p_manifest14, p_cose) 235 self.do_test_authentication() 236 237 def test_auth_5(self): 238 self.decode_file(p_test_vectors14[5], p_manifest14, p_cose) 239 self.do_test_authentication() 240 241 242class TestEx1Manifest14(TestManifest): 243 def __init__(self, *args, **kwargs): 244 super().__init__(*args, **kwargs) 245 self.decode_file(p_test_vectors14[1], p_manifest14, p_cose) 246 self.manifest_digest = bytes.fromhex("60c61d6eb7a1aaeddc49ce8157a55cff0821537eeee77a4ded44155b03045132") 247 248 def test_structure(self): 249 self.assertEqual("COSE_Sign1_Tagged", self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].union_choice) 250 self.assertEqual(-7, self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.Headers.protected.header_map_bstr.Generic_Headers.uint1union[0].int) 251 self.assertEqual(self.manifest_digest, self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr.suit_digest_bytes) 252 self.assertEqual(1, self.decoded.suit_manifest.suit_manifest_sequence_number) 253 self.assertEqual(bytes.fromhex("fa6b4a53d5ad5fdfbe9de663e4d41ffe"), self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[0].suit_parameter_vendor_identifier.RFC4122_UUID) 254 self.assertEqual(bytes.fromhex("1492af1425695e48bf429b2d51f2ab45"), self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[1].suit_parameter_class_identifier) 255 self.assertEqual(bytes.fromhex("00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210"), self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[2].suit_parameter_image_digest.suit_digest_bytes) 256 self.assertEqual('cose_alg_sha_256', self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[2].suit_parameter_image_digest.suit_digest_algorithm_id.union_choice) 257 self.assertEqual(34768, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[3].suit_parameter_image_size) 258 self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map)) 259 self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[1].SUIT_Condition.suit_condition_vendor_identifier.SUIT_Rep_Policy) 260 self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[2].SUIT_Condition.suit_condition_class_identifier.SUIT_Rep_Policy) 261 self.assertEqual(3, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union)) 262 self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0])) 263 self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands)) 264 self.assertEqual(1, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters)) 265 self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map)) 266 self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_common_sequence[0].suit_common_sequence.union[0].SUIT_Common_Commands.suit_directive_override_parameters.map[0])) 267 268 def test_cbor_pen(self): 269 data = bytes.fromhex(p_test_vectors14[1].read_text().replace("\n", "")) 270 struct = loads(data) 271 struct2 = loads(struct.value[3]) # manifest 272 struct3 = loads(struct2[3]) # common sequence 273 struct4 = loads(struct3[4]) # override params 274 self.assertEqual(struct4[0], 20) 275 self.assertTrue(isinstance(struct4[1][1], bytes)) 276 struct4[1][1] = cbor2.CBORTag(112, struct4[1][1]) # Add the tag for cbor-pen 277 struct3[4] = dumps(struct4) 278 struct2[3] = dumps(struct3) 279 struct.value[3] = dumps(struct2) 280 data = dumps(struct) 281 self.decode_string(data, p_manifest14, p_cose) 282 283 284class TestEx1InvManifest14(TestManifest): 285 def test_inv0(self): 286 data = bytes.fromhex(p_test_vectors14[1].read_text().replace("\n", "")) 287 struct = loads(data) 288 struct2 = loads(struct.value[2]) # authentication 289 struct3 = loads(struct2[1]) 290 struct3.tag = 99999 # invalid tag for COSE_Sign1 291 struct2[1] = dumps(struct3) 292 struct.value[2] = dumps(struct2) 293 data = dumps(struct) 294 try: 295 self.decode_string(data, p_manifest14, p_cose) 296 except zcbor.CddlValidationError as e: 297 return 298 else: 299 assert False, "Should have failed validation" 300 301 def test_inv1(self): 302 data = bytes.fromhex(p_test_vectors14[1].read_text().replace("\n", "")) 303 struct = loads(data) 304 struct2 = loads(struct.value[3]) # manifest 305 struct2[1] += 1 # invalid manifest version 306 struct.value[3] = dumps(struct2) 307 data = dumps(struct) 308 try: 309 self.decode_string(data, p_manifest14, p_cose) 310 except zcbor.CddlValidationError as e: 311 return 312 else: 313 assert False, "Should have failed validation" 314 315 def test_inv2(self): 316 data = bytes.fromhex(p_test_vectors14[1].read_text().replace("\n", "")) 317 struct = loads(data) 318 struct.value[23] = b'' # Invalid integrated payload key 319 data = dumps(struct) 320 try: 321 self.decode_string(data, p_manifest14, p_cose) 322 except (zcbor.CddlValidationError, cbor2.CBORDecodeEOF) as e: 323 return 324 else: 325 assert False, "Should have failed validation" 326 327 def test_inv3(self): 328 data = bytes.fromhex(p_test_vectors14[1].read_text().replace("\n", "")) 329 struct = loads(data) 330 struct2 = loads(struct.value[3]) # manifest 331 struct3 = loads(struct2[3]) # common sequence 332 struct4 = loads(struct3[4]) # override params 333 self.assertEqual(struct4[0], 20) 334 self.assertTrue(isinstance(struct4[1][1], bytes)) 335 struct4[1][1] += b'x' # vendor ID: wrong length 336 struct3[4] = dumps(struct4) 337 struct2[3] = dumps(struct3) 338 struct.value[3] = dumps(struct2) 339 data = dumps(struct) 340 try: 341 self.decode_string(data, p_manifest14, p_cose) 342 except zcbor.CddlValidationError as e: 343 return 344 else: 345 assert False, "Should have failed validation" 346 347 348class TestEx2Manifest14(TestManifest): 349 def __init__(self, *args, **kwargs): 350 super().__init__(*args, **kwargs) 351 self.decode_file(p_test_vectors14[2], p_manifest14, p_cose) 352 353 def test_text(self): 354 self.assertEqual( 355 bytes.fromhex('2bfc4d0cc6680be7dd9f5ca30aa2bb5d1998145de33d54101b80e2ca49faf918'), 356 self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_text[0].SUIT_Digest.suit_digest_bytes) 357 self.assertEqual( 358 bytes.fromhex('2bfc4d0cc6680be7dd9f5ca30aa2bb5d1998145de33d54101b80e2ca49faf918'), 359 sha256(dumps(self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text_bstr)).digest()) 360 self.assertEqual('arm.com', self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_vendor_domain[0]) 361 self.assertEqual('This component is a demonstration. The digest is a sample pattern, not a real one.', self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Component_Identifier[0].SUIT_Component_Identifier.SUIT_Text_Component_Keys.suit_text_component_description[0]) 362 363 # Check manifest description. The concatenation and .replace() call are there to add 364 # trailing whitespace to all blank lines except the first. 365 # This is done in this way to avoid editors automatically removing the whitespace. 366 self.assertEqual('''## Example 2: Simultaneous Download, Installation, Secure Boot, Severed Fields 367''' + ''' 368 This example covers the following templates: 369 370 * Compatibility Check ({{template-compatibility-check}}) 371 * Secure Boot ({{template-secure-boot}}) 372 * Firmware Download ({{firmware-download-template}}) 373 374 This example also demonstrates severable elements ({{ovr-severable}}), and text ({{manifest-digest-text}}).'''.replace("\n\n", "\n \n"), self.decoded.SUIT_Severable_Manifest_Members.suit_text[0].suit_text.SUIT_Text_Keys.suit_text_manifest_description[0]) 375 376 377class TestEx3Manifest14(TestManifest): 378 def __init__(self, *args, **kwargs): 379 super().__init__(*args, **kwargs) 380 self.decode_file(p_test_vectors14[3], p_manifest14, p_cose) 381 self.slots = (33792, 541696) 382 383 def test_try_each(self): 384 self.assertEqual(2, len(self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_install[0].SUIT_Command_Sequence_bstr.union[0].SUIT_Directive.suit_directive_try_each.SUIT_Directive_Try_Each_Argument.SUIT_Command_Sequence_bstr)) 385 self.assertEqual(self.slots[0], self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_install[0].SUIT_Command_Sequence_bstr.union[0].SUIT_Directive.suit_directive_try_each.SUIT_Directive_Try_Each_Argument.SUIT_Command_Sequence_bstr[0].union[0].SUIT_Directive.suit_directive_override_parameters.map[0].suit_parameter_component_slot) 386 self.assertEqual(self.slots[1], self.decoded.suit_manifest.SUIT_Severable_Members_Choice.suit_install[0].SUIT_Command_Sequence_bstr.union[0].SUIT_Directive.suit_directive_try_each.SUIT_Directive_Try_Each_Argument.SUIT_Command_Sequence_bstr[1].union[0].SUIT_Directive.suit_directive_override_parameters.map[0].suit_parameter_component_slot) 387 388 389class TestEx4Manifest14(TestManifest): 390 def __init__(self, *args, **kwargs): 391 super().__init__(*args, **kwargs) 392 self.decode_file(p_test_vectors14[4], p_manifest14, p_cose) 393 394 def test_components(self): 395 self.assertEqual(3, len(self.decoded.suit_manifest.suit_common.suit_components[0])) 396 self.assertEqual(b'\x00', self.decoded.suit_manifest.suit_common.suit_components[0][0].bstr[0]) 397 self.assertEqual(b'\x02', self.decoded.suit_manifest.suit_common.suit_components[0][1].bstr[0]) 398 self.assertEqual(b'\x01', self.decoded.suit_manifest.suit_common.suit_components[0][2].bstr[0]) 399 400 401class TestEx5Manifest14(TestManifest): 402 def __init__(self, *args, **kwargs): 403 super().__init__(*args, **kwargs) 404 self.decode_file(p_test_vectors14[5], p_manifest14, p_cose) 405 406 def test_validate(self): 407 self.assertEqual(4, len(self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_validate[0].suit_validate.union)) 408 self.assertEqual(15, self.decoded.suit_manifest.SUIT_Unseverable_Members.suit_validate[0].suit_validate.union[1].SUIT_Condition.suit_condition_image_match.SUIT_Rep_Policy) 409 410 411class TestEx5InvManifest14(TestManifest): 412 def test_invalid_rep_policy(self): 413 data = bytes.fromhex(p_test_vectors14[5].read_text().replace("\n", "")) 414 struct = loads(data) 415 struct2 = loads(struct.value[3]) # manifest 416 struct3 = loads(struct2[10]) # suit_validate 417 struct3[3] += 16 # invalid Rep_Policy 418 struct2[10] = dumps(struct3) 419 struct.value[3] = dumps(struct2) 420 data = dumps(struct) 421 try: 422 self.decode_string(data, p_manifest14, p_cose) 423 except zcbor.CddlValidationError as e: 424 return 425 else: 426 assert False, "Should have failed validation" 427 428 429class TestEx0Manifest16(TestEx0Manifest14): 430 def __init__(self, *args, **kwargs): 431 super().__init__(*args, **kwargs) 432 self.decode_file(p_test_vectors16[0], p_manifest16, p_cose) 433 434 435class TestEx1Manifest16(TestEx1Manifest14): 436 def __init__(self, *args, **kwargs): 437 super().__init__(*args, **kwargs) 438 self.decode_file(p_test_vectors16[1], p_manifest16, p_cose) 439 440 441class TestEx1InvManifest16(TestEx1InvManifest14): 442 def __init__(self, *args, **kwargs): 443 super().__init__(*args, **kwargs) 444 self.decode_file(p_test_vectors16[1], p_manifest16, p_cose) 445 446 447class TestEx2Manifest16(TestEx2Manifest14): 448 def __init__(self, *args, **kwargs): 449 super().__init__(*args, **kwargs) 450 self.decode_file(p_test_vectors16[2], p_manifest16, p_cose) 451 452 453class TestEx3Manifest16(TestEx3Manifest14): 454 def __init__(self, *args, **kwargs): 455 super().__init__(*args, **kwargs) 456 self.decode_file(p_test_vectors16[3], p_manifest16, p_cose) 457 458 459# Comment out because example 4 uses compression which is unsupported in manifest16 460# class TestEx4Manifest16(TestEx4Manifest14): 461# def __init__(self, *args, **kwargs): 462# super().__init__(*args, **kwargs) 463# self.decode_file(p_test_vectors16[4], p_manifest16, p_cose) 464 465 466class TestEx5Manifest16(TestEx5Manifest14): 467 def __init__(self, *args, **kwargs): 468 super().__init__(*args, **kwargs) 469 self.decode_file(p_test_vectors16[5], p_manifest16, p_cose) 470 471 472class TestEx5InvManifest16(TestEx5InvManifest14): 473 def __init__(self, *args, **kwargs): 474 super().__init__(*args, **kwargs) 475 self.decode_file(p_test_vectors16[5], p_manifest16, p_cose) 476 477 478class TestEx0Manifest20(TestEx0Manifest16): 479 def __init__(self, *args, **kwargs): 480 super().__init__(*args, **kwargs) 481 self.decode_file(p_test_vectors20[0], p_manifest20, p_cose) 482 483 484class TestEx1Manifest20(TestEx1Manifest16): 485 def __init__(self, *args, **kwargs): 486 super().__init__(*args, **kwargs) 487 self.decode_file(p_test_vectors20[1], p_manifest20, p_cose) 488 self.manifest_digest = bytes.fromhex("ef14b7091e8adae8aa3bb6fca1d64fb37e19dcf8b35714cfdddc5968c80ff50e") 489 490 def test_structure(self): 491 self.assertEqual("COSE_Sign1_Tagged", self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].union_choice) 492 self.assertEqual(-7, self.decoded.suit_authentication_wrapper.SUIT_Authentication_Block_bstr[0].COSE_Sign1_Tagged.Headers.protected.header_map_bstr.Generic_Headers.uint1union[0].int) 493 self.assertEqual(self.manifest_digest, self.decoded.suit_authentication_wrapper.SUIT_Digest_bstr.suit_digest_bytes) 494 self.assertEqual(1, self.decoded.suit_manifest.suit_manifest_sequence_number) 495 self.assertEqual(bytes.fromhex("fa6b4a53d5ad5fdfbe9de663e4d41ffe"), self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map[0].suit_parameter_vendor_identifier.RFC4122_UUID) 496 self.assertEqual(bytes.fromhex("1492af1425695e48bf429b2d51f2ab45"), self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map[1].suit_parameter_class_identifier) 497 self.assertEqual(bytes.fromhex("00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210"), self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map[2].suit_parameter_image_digest.suit_digest_bytes) 498 self.assertEqual('cose_alg_sha_256', self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map[2].suit_parameter_image_digest.suit_digest_algorithm_id.union_choice) 499 self.assertEqual(34768, self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map[3].suit_parameter_image_size) 500 self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map)) 501 self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[1].SUIT_Condition.suit_condition_vendor_identifier.SUIT_Rep_Policy) 502 self.assertEqual(15, self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[2].SUIT_Condition.suit_condition_class_identifier.SUIT_Rep_Policy) 503 self.assertEqual(3, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union)) 504 self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0])) 505 self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands)) 506 self.assertEqual(1, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters)) 507 self.assertEqual(4, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map)) 508 self.assertEqual(2, len(self.decoded.suit_manifest.suit_common.suit_shared_sequence[0].suit_shared_sequence.union[0].SUIT_Shared_Commands.suit_directive_override_parameters.map[0])) 509 510 511class TestEx1InvManifest20(TestEx1InvManifest16): 512 def __init__(self, *args, **kwargs): 513 super().__init__(*args, **kwargs) 514 self.decode_file(p_test_vectors20[1], p_manifest20, p_cose) 515 516 517class TestEx2Manifest20(TestEx2Manifest16): 518 def __init__(self, *args, **kwargs): 519 super().__init__(*args, **kwargs) 520 self.decode_file(p_test_vectors20[2], p_manifest20, p_cose) 521 522 523class TestEx3Manifest20(TestEx3Manifest16): 524 def __init__(self, *args, **kwargs): 525 super().__init__(*args, **kwargs) 526 self.decode_file(p_test_vectors20[3], p_manifest20, p_cose) 527 self.slots = (0, 1) 528 529 530class TestEx4Manifest20(TestEx4Manifest14): 531 def __init__(self, *args, **kwargs): 532 super().__init__(*args, **kwargs) 533 self.decode_file(p_test_vectors20[4], p_manifest20, p_cose) 534 535 536class TestEx5Manifest20(TestEx5Manifest16): 537 def __init__(self, *args, **kwargs): 538 super().__init__(*args, **kwargs) 539 self.decode_file(p_test_vectors20[5], p_manifest20, p_cose) 540 541 542class TestEx5InvManifest20(TestEx5InvManifest16): 543 def __init__(self, *args, **kwargs): 544 super().__init__(*args, **kwargs) 545 self.decode_file(p_test_vectors20[5], p_manifest20, p_cose) 546 547 548class PopenTest(TestCase): 549 def popen_test(self, args, input="", exp_retcode=0): 550 call0 = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) 551 stdout0, stderr0 = call0.communicate(input) 552 self.assertEqual(exp_retcode, call0.returncode, stderr0.decode('utf-8')) 553 return stdout0, stderr0 554 555 556class TestCLI(PopenTest): 557 def get_std_args(self, input, cmd="convert"): 558 return ["zcbor", cmd, "--cddl", str(p_manifest12), "--input", str(input), "-t", "SUIT_Envelope_Tagged", "--yaml-compatibility"] 559 560 def do_testManifest(self, n): 561 self.popen_test(self.get_std_args(p_test_vectors12[n], cmd="validate"), "") 562 stdout0, _ = self.popen_test(self.get_std_args(p_test_vectors12[n]) + ["--output", "-", "--output-as", "cbor"], "") 563 564 self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "cbor"], stdout0) 565 stdout1, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "cbor", "--output", "-", "--output-as", "json"], stdout0) 566 567 self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "json"], stdout1) 568 stdout2, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "json", "--output", "-", "--output-as", "yaml"], stdout1) 569 570 self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "yaml"], stdout2) 571 stdout3, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "yaml", "--output", "-", "--output-as", "cbor"], stdout2) 572 573 self.assertEqual(stdout0, stdout3) 574 575 self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "cbor"], stdout3) 576 stdout4, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "cbor", "--output", "-", "--output-as", "cborhex"], stdout3) 577 578 self.popen_test(self.get_std_args("-", cmd="validate") + ["--input-as", "cborhex"], stdout4) 579 stdout5, _ = self.popen_test(self.get_std_args("-") + ["--input-as", "cborhex", "--output", "-", "--output-as", "json"], stdout4) 580 581 self.assertEqual(stdout1, stdout5) 582 583 self.maxDiff = None 584 585 with open(p_test_vectors12[n], 'r') as f: 586 self.assertEqual(sub(r"\W+", "", f.read()), sub(r"\W+", "", stdout4.decode("utf-8"))) 587 588 def test_0(self): 589 self.do_testManifest(0) 590 591 def test_1(self): 592 self.do_testManifest(1) 593 594 def test_2(self): 595 self.do_testManifest(2) 596 597 def test_3(self): 598 self.do_testManifest(3) 599 600 def test_4(self): 601 self.do_testManifest(4) 602 603 def test_5(self): 604 self.do_testManifest(5) 605 606 def test_map_bstr(self): 607 stdout1, _ = self.popen_test(["zcbor", "convert", "--cddl", str(p_map_bstr_cddl), "--input", str(p_map_bstr_yaml), "-t", "map", "--yaml-compatibility", "--output", "-"], "") 608 self.assertEqual(dumps({"test": bytes.fromhex("1234abcd"), "test2": cbor2.CBORTag(1234, bytes.fromhex("1a2b3c4d")), ("test3",): dumps(1234)}), stdout1) 609 610 def test_decode_encode(self): 611 _, stderr1 = self.popen_test(["zcbor", "code", "--cddl", str(p_map_bstr_cddl), "-t", "map"], "", exp_retcode=2) 612 self.assertIn(b"error: Please specify at least one of --decode or --encode", stderr1) 613 614 def test_output_present(self): 615 args = ["zcbor", "code", "--cddl", str(p_map_bstr_cddl), "-t", "map", "-d"] 616 _, stderr1 = self.popen_test(args, "", exp_retcode=2) 617 self.assertIn( 618 b"error: Please specify both --output-c and --output-h " 619 b"unless --output-cmake is specified.", 620 stderr1) 621 622 _, stderr2 = self.popen_test(args + ["--output-c", "/tmp/map.c"], "", exp_retcode=2) 623 self.assertIn( 624 b"error: Please specify both --output-c and --output-h " 625 b"unless --output-cmake is specified.", 626 stderr2) 627 628 629class TestOptional(TestCase): 630 def test_optional_0(self): 631 with open(p_optional, 'r') as f: 632 cddl_res = zcbor.DataTranslator.from_cddl(f.read(), 16) 633 cddl = cddl_res.my_types['cfg'] 634 test_yaml = """ 635 mem_config: 636 - 0 637 - 5""" 638 decoded = cddl.decode_str_yaml(test_yaml) 639 self.assertEqual(decoded.mem_config[0].READ.union_choice, "uint0") 640 self.assertEqual(decoded.mem_config[0].N, [5]) 641 642 643class TestUndefined(TestCase): 644 def test_undefined_0(self): 645 cddl_res = zcbor.DataTranslator.from_cddl( 646 p_prelude.read_text() + '\n' + p_corner_cases.read_text(), 16) 647 cddl = cddl_res.my_types['Simples'] 648 test_yaml = "[true, false, true, null, [zcbor_undefined]]" 649 650 decoded = cddl.decode_str_yaml(test_yaml, yaml_compat=True) 651 self.assertEqual(True, decoded.boolval) 652 653 encoded = cddl.str_to_yaml(cddl.from_yaml(test_yaml, yaml_compat=True), yaml_compat=True) 654 self.assertEqual(safe_load(encoded), safe_load(test_yaml)) 655 656 657class TestFloat(TestCase): 658 def test_float_0(self): 659 cddl_res = zcbor.DataTranslator.from_cddl( 660 p_prelude.read_text() + '\n' + p_corner_cases.read_text(), 16) 661 cddl = cddl_res.my_types['Floats'] 662 test_yaml = f"[3.1415, 1234567.89, 0.000123, 3.1415, 2.71828, 5.0, {1/3}]" 663 664 decoded = cddl.decode_str_yaml(test_yaml) 665 self.assertEqual(3.1415, decoded.float_16) 666 self.assertEqual(1234567.89, decoded.float_32) 667 self.assertEqual(0.000123, decoded.float_64) 668 self.assertEqual(2, len(decoded.floats)) 669 self.assertEqual(5, decoded.floats[0]) 670 self.assertEqual(1 / 3, decoded.floats[1]) 671 672 encoded = cddl.str_to_yaml(cddl.from_yaml(test_yaml)) 673 self.assertEqual(safe_load(encoded), safe_load(test_yaml)) 674 675 676class TestYamlCompatibility(PopenTest): 677 def test_yaml_compatibility(self): 678 self.popen_test(["zcbor", "validate", "-c", p_yaml_compat_cddl, "-i", p_yaml_compat_yaml, "-t", "Yaml_compatibility_example"], exp_retcode=1) 679 self.popen_test(["zcbor", "validate", "-c", p_yaml_compat_cddl, "-i", p_yaml_compat_yaml, "-t", "Yaml_compatibility_example", "--yaml-compatibility"]) 680 stdout1, _ = self.popen_test(["zcbor", "convert", "-c", p_yaml_compat_cddl, "-i", p_yaml_compat_yaml, "-o", "-", "-t", "Yaml_compatibility_example", "--yaml-compatibility"]) 681 stdout2, _ = self.popen_test(["zcbor", "convert", "-c", p_yaml_compat_cddl, "-i", "-", "-o", "-", "--output-as", "yaml", "-t", "Yaml_compatibility_example", "--yaml-compatibility"], stdout1) 682 self.assertEqual(safe_load(stdout2), safe_load(p_yaml_compat_yaml.read_text())) 683 684 685class TestIntmax(TestCase): 686 def test_intmax1(self): 687 cddl_res = zcbor.DataTranslator.from_cddl( 688 p_prelude.read_text() + '\n' + p_corner_cases.read_text(), 16) 689 cddl = cddl_res.my_types['Intmax1'] 690 test_yaml = f"[-128, 127, 255, -32768, 32767, 65535, -2147483648, 2147483647, 4294967295, -9223372036854775808, 9223372036854775807, 18446744073709551615]" 691 decoded = cddl.decode_str_yaml(test_yaml) 692 693 def test_intmax2(self): 694 cddl_res = zcbor.DataTranslator.from_cddl( 695 p_prelude.read_text() + '\n' + p_corner_cases.read_text(), 16) 696 cddl = cddl_res.my_types['Intmax2'] 697 test_yaml1 = f"[-128, 0, -32768, 0, -2147483648, 0, -9223372036854775808, 0]" 698 decoded = cddl.decode_str_yaml(test_yaml1) 699 self.assertEqual(decoded.INT_8, -128) 700 self.assertEqual(decoded.UINT_8, 0) 701 self.assertEqual(decoded.INT_16, -32768) 702 self.assertEqual(decoded.UINT_16, 0) 703 self.assertEqual(decoded.INT_32, -2147483648) 704 self.assertEqual(decoded.UINT_32, 0) 705 self.assertEqual(decoded.INT_64, -9223372036854775808) 706 self.assertEqual(decoded.UINT_64, 0) 707 708 test_yaml2 = f"[127, 255, 32767, 65535, 2147483647, 4294967295, 9223372036854775807, 18446744073709551615]" 709 decoded = cddl.decode_str_yaml(test_yaml2) 710 self.assertEqual(decoded.INT_8, 127) 711 self.assertEqual(decoded.UINT_8, 255) 712 self.assertEqual(decoded.INT_16, 32767) 713 self.assertEqual(decoded.UINT_16, 65535) 714 self.assertEqual(decoded.INT_32, 2147483647) 715 self.assertEqual(decoded.UINT_32, 4294967295) 716 self.assertEqual(decoded.INT_64, 9223372036854775807) 717 self.assertEqual(decoded.UINT_64, 18446744073709551615) 718 719 720if __name__ == "__main__": 721 main() 722