From 7f6862c2d3b28b1cfd750c4351a851c82b60f8b8 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Fri, 5 Jul 2019 11:56:26 +0200 Subject: [PATCH] suit: add SUIT draft v4 manifest tools This commit adds support tools used by the SUIT firmware upgrade module. Co-authored-by: Alexandre Abadie Co-authored-by: Koen Zandberg Co-authored-by: Francisco Molina --- dist/tools/suit_v4/gen_key.py | 33 ++ dist/tools/suit_v4/gen_manifest.py | 87 ++++ dist/tools/suit_v4/sign-04.py | 154 +++++++ .../tools/suit_v4/suit_manifest_encoder_04.py | 411 ++++++++++++++++++ dist/tools/suit_v4/test-2img.json | 33 ++ 5 files changed, 718 insertions(+) create mode 100755 dist/tools/suit_v4/gen_key.py create mode 100755 dist/tools/suit_v4/gen_manifest.py create mode 100755 dist/tools/suit_v4/sign-04.py create mode 100644 dist/tools/suit_v4/suit_manifest_encoder_04.py create mode 100644 dist/tools/suit_v4/test-2img.json diff --git a/dist/tools/suit_v4/gen_key.py b/dist/tools/suit_v4/gen_key.py new file mode 100755 index 0000000000..1d940d3809 --- /dev/null +++ b/dist/tools/suit_v4/gen_key.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2019 Inria +# 2019 FU Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +import sys +import ed25519 + + +def main(): + if len(sys.argv) != 3: + print("usage: gen_key.py ") + sys.exit(1) + + _signing_key, _verifying_key = ed25519.create_keypair() + with open(sys.argv[1], "wb") as f: + f.write(_signing_key.to_bytes()) + + with open(sys.argv[2], "wb") as f: + f.write(_verifying_key.to_bytes()) + + vkey_hex = _verifying_key.to_ascii(encoding="hex") + print("Generated public key: '{}'".format(vkey_hex.decode())) + + +if __name__ == '__main__': + main() diff --git a/dist/tools/suit_v4/gen_manifest.py b/dist/tools/suit_v4/gen_manifest.py new file mode 100755 index 0000000000..eaf825d4c3 --- /dev/null +++ b/dist/tools/suit_v4/gen_manifest.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2019 Inria +# 2019 FU Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +import os +import hashlib +import json +import uuid + +import click + +from suit_manifest_encoder_04 import compile_to_suit + + +def str2int(x): + if x.startswith("0x"): + return int(x, 16) + else: + return x + + +def sha256_from_file(filepath): + sha256 = hashlib.sha256() + sha256.update(open(filepath, "rb").read()) + return sha256.digest() + + +@click.command() +@click.option("--template", "-t", required=True, type=click.File()) +@click.option("--urlroot", "-u", required=True, type=click.STRING) +@click.option("--offsets", "-O", required=True, type=click.STRING) +@click.option("--seqnr", "-s", required=True, type=click.INT) +@click.option("--output", "-o", type=click.File(mode="wb")) +@click.option("--uuid-vendor", "-V", required=True) +@click.option("--uuid-class", "-C", required=True) +@click.option("--keyfile", "-K", required=False, type=click.File()) +@click.argument("slotfiles", nargs=2, type=click.Path()) +def main(template, urlroot, offsets, slotfiles, output, seqnr, uuid_vendor, + uuid_class, keyfile): + + uuid_vendor = uuid.uuid5(uuid.NAMESPACE_DNS, uuid_vendor) + uuid_class = uuid.uuid5(uuid_vendor, uuid_class) + template = json.load(template) + slotfiles = list(slotfiles) + + template["sequence-number"] = seqnr + template["conditions"] = [ + {"condition-vendor-id": uuid_vendor.hex}, + {"condition-class-id": uuid_class.hex}, + ] + + offsets = offsets.split(",") + offsets = [str2int(x) for x in offsets] + + for slot, slotfile in enumerate(slotfiles): + filename = slotfile + size = os.path.getsize(filename) + uri = os.path.join(urlroot, os.path.basename(filename)) + offset = offsets[slot] + + _image_slot = template["components"][0]["images"][slot] + _image_slot.update({ + "file": filename, + "uri": uri, + "size": size, + "digest": sha256_from_file(slotfile), + }) + + _image_slot["conditions"][0]["condition-component-offset"] = offset + _image_slot["file"] = filename + + result = compile_to_suit(template) + if output: + output.write(result) + else: + print(result) + + +if __name__ == "__main__": + main() diff --git a/dist/tools/suit_v4/sign-04.py b/dist/tools/suit_v4/sign-04.py new file mode 100755 index 0000000000..db872b305f --- /dev/null +++ b/dist/tools/suit_v4/sign-04.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------------- +# Copyright 2018-2019 ARM Limited or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- +""" +This is a demo script that is intended to act as a reference for SUIT manifest +signing. + +NOTE: It is expected that C and C++ parser implementations will be written +against this script, so it does not adhere to PEP8 in order to maintain +similarity between the naming in this script and that of C/C++ implementations. +""" + +import sys +import copy + +import cbor +import ed25519 + +from pprint import pprint + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization + +# Private key in arg 1 +# Public key in arg 2 +# Input file in arg 3 +# Output file in arg 4 + +COSE_Sign_Tag = 98 +APPLICATION_OCTET_STREAM_ID = 42 +ES256 = -7 +EDDSA = -8 + + +def signWrapper(algo, private_key, public_key, encwrapper): + wrapper = cbor.loads(encwrapper) + + pprint(wrapper[1]) + COSE_Sign = copy.deepcopy(wrapper[1]) + if not COSE_Sign: + protected = cbor.dumps({ + 3: APPLICATION_OCTET_STREAM_ID, # Content Type + }) + unprotected = { + } + signatures = [] + + # Create a COSE_Sign_Tagged object + COSE_Sign = [ + protected, + unprotected, + b'', + signatures + ] + + if algo == EDDSA: + public_bytes = public_key.to_bytes() + else: + public_bytes = public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo) + + # NOTE: Using RFC7093, Method 4 + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(public_bytes) + kid = digest.finalize() + # Sign the payload + protected = cbor.dumps({ + 1: algo, # alg + }) + # Create the signing object + unprotected = { + 4: kid # kid + } + + Sig_structure = [ + "Signature", # Context + COSE_Sign[0], # Body Protected + protected, # signature protected + b'', # External AAD + wrapper[2] # payload + ] + sig_str = cbor.dumps(Sig_structure, sort_keys=True) + + if algo == EDDSA: + signature = private_key.sign(sig_str) + else: + signature = private_key.sign( + sig_str, + ec.ECDSA(hashes.SHA256()) + ) + + COSE_Signature = [ + protected, + unprotected, + signature + ] + COSE_Sign[3].append(COSE_Signature) + wrapper[1] = cbor.dumps(cbor.Tag(COSE_Sign_Tag, COSE_Sign), sort_keys=True) + return wrapper + + +def main(): + private_key = None + algo = ES256 + with open(sys.argv[1], 'rb') as fd: + priv_key_bytes = fd.read() + try: + private_key = serialization.load_pem_private_key( + priv_key_bytes, password=None, backend=default_backend()) + except ValueError: + algo = EDDSA + private_key = ed25519.SigningKey(priv_key_bytes) + + public_key = None + with open(sys.argv[2], 'rb') as fd: + pub_key_bytes = fd.read() + try: + public_key = serialization.load_pem_public_key( + pub_key_bytes, backend=default_backend()) + except ValueError: + public_key = ed25519.VerifyingKey(pub_key_bytes) + + # Read the input file + doc = None + with open(sys.argv[3], 'rb') as fd: + doc = fd.read() + + outDoc = signWrapper(algo, private_key, public_key, doc) + + with open(sys.argv[4], 'wb') as fd: + fd.write(cbor.dumps(outDoc, sort_keys=True)) + + +if __name__ == '__main__': + main() diff --git a/dist/tools/suit_v4/suit_manifest_encoder_04.py b/dist/tools/suit_v4/suit_manifest_encoder_04.py new file mode 100644 index 0000000000..f071f0412b --- /dev/null +++ b/dist/tools/suit_v4/suit_manifest_encoder_04.py @@ -0,0 +1,411 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------------- +# Copyright 2019 ARM Limited or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- +import json +import cbor +import binascii +import uuid +import os + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization + +COSE_ALG = 1 +COSE_Sign_Tag = 98 +APPLICATION_OCTET_STREAM_ID = 42 +ES256 = -7 +EDDSA = -8 + +SUIT_Authentication_Wrapper = 1 +SUIT_Manifest = 2 +SUIT_Dependency_Resolution = 7 +SUIT_Payload_Fetch = 8 +SUIT_Install = 9 +SUIT_Text = 13 +SUIT_Coswid = 14 + +SUIT_Manifest_Version = 1 +SUIT_Manifest_Sequence_Number = 2 +SUIT_Dependencies = 3 +SUIT_Components = 4 +SUIT_Dependency_Components = 5 +SUIT_Common = 6 +SUIT_Dependency_Resolution = 7 +SUIT_Payload_Fetch = 8 +SUIT_Install = 9 +SUIT_Validate = 10 +SUIT_Load = 11 +SUIT_Run = 12 +SUIT_Text = 13 +SUIT_Coswid = 14 + +SUIT_Dependency_Digest = 1 +SUIT_Dependency_Prefix = 2 + +SUIT_Component_Identifier = 1 +SUIT_Component_Size = 2 +SUIT_Component_Digest = 3 + +SUIT_Component_Dependency_Index = 2 + +SUIT_Condition_Vendor_Identifier = 1 +SUIT_Condition_Class_Identifier = 2 +SUIT_Condition_Device_Identifier = 3 +SUIT_Condition_Image_Match = 4 +SUIT_Condition_Image_Not_Match = 5 +SUIT_Condition_Use_Before = 6 +SUIT_Condition_Minimum_Battery = 7 +SUIT_Condition_Update_Authorised = 8 +SUIT_Condition_Version = 9 +SUIT_Condition_Component_Offset = 10 + +SUIT_Directive_Set_Component_Index = 11 +SUIT_Directive_Set_Manifest_Index = 12 +SUIT_Directive_Run_Sequence = 13 +SUIT_Directive_Run_Sequence_Conditional = 14 +SUIT_Directive_Process_Dependency = 15 +SUIT_Directive_Set_Parameters = 16 +SUIT_Directive_Override_Parameters = 19 +SUIT_Directive_Fetch = 20 +SUIT_Directive_Copy = 21 +SUIT_Directive_Run = 22 +SUIT_Directive_Wait = 23 + +SUIT_Parameter_Strict_Order = 1 +SUIT_Parameter_Coerce_Condition_Failure = 2 +SUIT_Parameter_Vendor_ID = 3 +SUIT_Parameter_Class_ID = 4 +SUIT_Parameter_Device_ID = 5 +SUIT_Parameter_URI_List = 6 +SUIT_Parameter_Encryption_Info = 7 +SUIT_Parameter_Compression_Info = 8 +SUIT_Parameter_Unpack_Info = 9 +SUIT_Parameter_Source_Component = 10 +SUIT_Parameter_Image_Digest = 11 +SUIT_Parameter_Image_Size = 12 + +SUIT_Compression_Algorithm = 1 + +def obj2bytes(o): + if isinstance(o, int): + l = [] + while o: + l.append(o&0xff) + o = o >> 8 + return bytes(l) + if isinstance(o, str): + return o.encode('utf-8') + if isinstance(o, bytes): + return o + return b'' + +def make_SUIT_Components(unused, components): + comps = [] + for component in components: + c = { + SUIT_Component_Identifier : [obj2bytes(x) for x in component["id"]] + } + if "digest" in component: + c[SUIT_Component_Digest] = [1, binascii.a2b_hex(component["digest"])] + if "size" in component: + c[SUIT_Component_Size] = component["size"] + comps.append(c) + return (SUIT_Components, comps) + +def make_SUIT_Compression_Info(info): + algorithms = { + 'gzip' : 1, + 'bzip2' : 2, + 'deflate' : 3, + 'lz4' : 4, + 'lzma' : 7, + } + cinfo = { + SUIT_Compression_Algorithm :algorithms[info['algorithm']] + } + +def make_SUIT_Set_Parameters(parameters): + set_parameters = {} + SUIT_Parameters_Keys = { + # SUIT_Parameter_Strict_Order = 1 + # SUIT_Parameter_Coerce_Condition_Failure = 2 + # SUIT_Parameter_Vendor_ID = 3 + # SUIT_Parameter_Class_ID = 4 + # SUIT_Parameter_Device_ID = 5 + # SUIT_Parameter_URI_List = 6 + 'uris' : lambda x: (SUIT_Parameter_URI_List, cbor.dumps(x)), + # SUIT_Parameter_Encryption_Info = 7 + # SUIT_Parameter_Compression_Info = 8 + 'compression-info': lambda x : ( + SUIT_Parameter_Compression_Info, + cbor.dumps(make_SUIT_Compression_Info(x)) + ), + # SUIT_Parameter_Unpack_Info = 9 + 'source-index' : lambda x :(SUIT_Parameter_Source_Component, int(x)), + 'image-digest' : lambda x :(SUIT_Parameter_Image_Digest, cbor.dumps(x, sort_keys=True)), + 'image-size' : lambda x :(SUIT_Parameter_Image_Size, int(x)), + } + for p in parameters: + if p in SUIT_Parameters_Keys: + k, v = SUIT_Parameters_Keys[p](parameters[p]) + set_parameters[k] = v + else: + raise Exception('ERROR: {} not found!'.format(p)) + + return (SUIT_Directive_Set_Parameters, set_parameters) + +def make_SUIT_Sequence(seq_name, sequence): + seq = [] + SUIT_Sequence_Keys = { + "condition-vendor-id" : lambda x : (SUIT_Condition_Vendor_Identifier, uuid.UUID(x).bytes), + "condition-class-id" : lambda x : (SUIT_Condition_Class_Identifier, uuid.UUID(x).bytes), + "condition-device-id" : lambda x : (SUIT_Condition_Device_Identifier, uuid.UUID(x).bytes), + "condition-image" : lambda x : (SUIT_Condition_Image_Match, None), + "condition-not-image" : lambda x : (SUIT_Condition_Image_Not_Match, None), + # SUIT_Condition_Use_Before = 6 + # SUIT_Condition_Minimum_Battery = 7 + # SUIT_Condition_Update_Authorised = 8 + # SUIT_Condition_Version = 9 + "condition-component-offset" : lambda x: (SUIT_Condition_Component_Offset, int(x)), + # + "directive-set-component" : lambda x : (SUIT_Directive_Set_Component_Index, x), + # SUIT_Directive_Set_Manifest_Index = 12 + # SUIT_Directive_Run_Sequence = 13 + # SUIT_Directive_Run_Sequence_Conditional = 14 + "directive-run-conditional" : lambda x : ( + SUIT_Directive_Run_Sequence_Conditional, + cbor.dumps(make_SUIT_Sequence("conditional-sequence", x), sort_keys = True) + ), + # SUIT_Directive_Process_Dependency = 15 + # SUIT_Directive_Set_Parameters = 16 + "directive-set-var" : make_SUIT_Set_Parameters, + # SUIT_Directive_Override_Parameters = 19 + "directive-fetch" : lambda x : (SUIT_Directive_Fetch, None), + "directive-copy" : lambda x : (SUIT_Directive_Copy, None), + "directive-run" : lambda x : (SUIT_Directive_Run, None), + # SUIT_Directive_Wait = 23 + } + for command in sequence: + com_dict = {} + for c in command: + if c in SUIT_Sequence_Keys: + k, v = SUIT_Sequence_Keys[c](command[c]) + com_dict[k] = v + else: + raise Exception('ERROR: {} not found!'.format(c)) + seq.append(com_dict) + return seq + +def make_SUIT_Manifest(info): + # print(info) + SUIT_Manifest_Keys = { + "structure-version" : lambda y, x: (SUIT_Manifest_Version, x), + "sequence-number" : lambda y, x: (SUIT_Manifest_Sequence_Number, x), + # SUIT_Dependencies = 3 + "components" : make_SUIT_Components, + # SUIT_Dependency_Components = 5 + "common" : lambda y, x: (SUIT_Common, cbor.dumps(make_SUIT_Sequence(y, x), sort_keys=True)), + # SUIT_Dependency_Resolution = 7 + # SUIT_Payload_Fetch = 8 + "apply-image" : lambda y, x: (SUIT_Install, cbor.dumps(make_SUIT_Sequence(y, x), sort_keys=True)), + "system-verification": lambda y, x: (SUIT_Validate, cbor.dumps(make_SUIT_Sequence(y, x), sort_keys=True)), + "load-image" : lambda y, x: (SUIT_Load, cbor.dumps(make_SUIT_Sequence(y, x), sort_keys=True)), + "run-image" : lambda y, x: (SUIT_Run, cbor.dumps(make_SUIT_Sequence(y, x), sort_keys=True)), + # SUIT_Text = 13 + # SUIT_Coswid = 14 + } + manifest = {} + for field in info: + if field in SUIT_Manifest_Keys: + k, v = SUIT_Manifest_Keys[field](field, info[field]) + manifest[k] = v + else: + raise Exception('ERROR: {} not found!'.format(field)) + + # print ('suit-manifest: {}'.format(manifest)) + return manifest + +def make_SUIT_Outer_Wrapper(info): + Outer_Wrapper = { + SUIT_Authentication_Wrapper : None, + SUIT_Manifest : cbor.dumps(make_SUIT_Manifest(info), sort_keys = True) + } + # print('Outer_Wrapper: {}'.format(Outer_Wrapper)) + return Outer_Wrapper + + +def make_SUIT_Components(unused, components): + comps = [] + for component in components: + c = { + SUIT_Component_Identifier : [obj2bytes(x) for x in component["id"]] + } + if "digest" in component: + c[SUIT_Component_Digest] = [1, binascii.a2b_hex(component["digest"])] + if "size" in component: + c[SUIT_Component_Size] = component["size"] + comps.append(c) + return (SUIT_Components, comps) + +# Expected input format: +# { +# "digest-type" : "str", +# "structure-version" : 1, +# "sequence-number" : 2, +# "components": [ +# { +# "component-id":[bytes()], +# "bootable" : bool(), +# "images" : [ +# { +# "conditions" : [ +# {"current-digest": bytes()}, +# {"target-offset" : int()}, +# {"target-id": [bytes()]}, +# ], +# "digest": bytes(), +# "size" : int(), +# "uri" : str(), +# } +# ] +# "conditions" : [ +# {"current-digest": bytes()}, +# {"vendor-id" : bytes()}, +# {"class-id" : bytes()}, +# {"device-id" : bytes()}, +# ] +# } +# ], +# "conditions" : [ +# {"vendor-id" : bytes()}, +# {"class-id" : bytes()}, +# {"device-id" : bytes()}, +# ] +# } +def digest_str_to_id(s): + return { + 'sha-256' : 1, + 'sha-256-128' : 2, + 'sha-256-120' : 3, + 'sha-256-96' : 4, + 'sha-256-64' : 5, + 'sha-256-32' : 6, + 'sha-384' : 7, + 'sha-512' : 8, + 'sha3-224' : 9, + 'sha3-256' : 10, + 'sha3-384' : 11, + 'sha3-512' : 12, + }.get(s, 1) + +def compile_to_suit(suit_info): + digest_id = digest_str_to_id(suit_info.get('digest-type', 'sha-256')) + suit_manifest_desc = { + 'structure-version':int(suit_info.get('structure-version', 1)), + 'sequence-number':int(suit_info['sequence-number']), + } + #TODO: Dependencies + # Components + components = [] + #TODO: dependency components + common = [] + dependency_fetch = None + #TODO: Image Fetch when not in streaming mode + fetch_image = None + apply_image = [] + # System Verification + #TODO: Dependencies + system_verification = [ + {"directive-set-component": True}, + {"condition-image": None}, + ] + #TODO: Load Image + load_image = None + run_image = [] + + for con in suit_info.get('conditions', []): + common.append(con) + # for each component + for i, comp in enumerate(suit_info['components']): + comp_info = { + 'id' : comp['id'] + } + components.append(comp_info) + if len(comp['images']) == 1: + set_comp = {"directive-set-component": i} + set_params = { + "directive-set-var" : { + "image-size" : int(comp['images'][0]['size']), + "image-digest" : [digest_id, bytes(comp['images'][0]['digest'])] + } + } + common.append(set_comp) + common.append(set_params) + set_params = { + "directive-set-var" : { + "uris" : [[0, str(comp['images'][0]['uri'])]] + } + } + apply_image.append(set_comp) + apply_image.append(set_params) + else: + for image in comp['images']: + set_comp = {"directive-set-component": i} + set_params = { + "directive-set-var" : { + "image-size" : int(image['size']), + "image-digest" : [digest_id, bytes(image['digest'])] + } + } + conditional_seq = [set_comp] + image.get('conditions',[])[:] + [set_params] + conditional_set_params = { + 'directive-run-conditional': conditional_seq + } + common.append(conditional_set_params) + set_params = { + "directive-set-var" : { + "uris" : [[0, str(image['uri'])]] + } + } + conditional_seq = [set_comp] + image.get('conditions',[])[:] + [set_params] + conditional_set_params = { + 'directive-run-conditional': conditional_seq + } + + apply_image.append(conditional_set_params) + if comp.get('bootable', False): + run_image.append({'directive-set-component' : i}) + run_image.append({'directive-run':None}) + + apply_image.append({"directive-set-component": True}) + apply_image.append({"directive-fetch": None}) + + suit_manifest_desc.update({ + "components" : components, + "common" : common, + "apply-image" : apply_image, + "system-verification": system_verification, + "run-image" : run_image, + }) + + print(suit_manifest_desc) + + return cbor.dumps(make_SUIT_Outer_Wrapper(suit_manifest_desc), sort_keys=True) diff --git a/dist/tools/suit_v4/test-2img.json b/dist/tools/suit_v4/test-2img.json new file mode 100644 index 0000000000..fc18289b3d --- /dev/null +++ b/dist/tools/suit_v4/test-2img.json @@ -0,0 +1,33 @@ +{ + "digest-type" : "sha-256", + "structure-version" : 1, + "sequence-number" : 2, + "components": [ + { + "id":["00"], + "bootable" : true, + "images" : [ + { + "file" : "encode-04.py", + "uri" : "http://example.com/file.bin", + "conditions" : [ + {"condition-component-offset":4096} + ] + + }, + { + "file" : "suit_manifest_encoder_04.py", + "uri" : "http://example.com/file1.bin", + "conditions" : [ + {"condition-component-offset":8192} + ] + + } + ] + } + ], + "conditions" : [ + {"condition-vendor-id" : "fa6b4a53-d5ad-5fdf-be9d-e663e4d41ffe"}, + {"condition-class-id" : "1492af14-2569-5e48-bf42-9b2d51f2ab45"} + ] +}