From 0dd7aed199901f45be34cc967323346b053508d7 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Sat, 5 Nov 2022 13:39:41 +0000 Subject: [PATCH 1/3] Add support for adding and removing suit envelope tags --- suit_tool/argparser.py | 11 +++++++++ suit_tool/clidriver.py | 3 ++- suit_tool/tag.py | 51 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 suit_tool/tag.py diff --git a/suit_tool/argparser.py b/suit_tool/argparser.py index e6e9911..a28cf43 100644 --- a/suit_tool/argparser.py +++ b/suit_tool/argparser.py @@ -92,6 +92,17 @@ class MainArgumentParser(object): sever_parser.add_argument('-e', '--element', action='append', type=str, dest='elements', default=[]) sever_parser.add_argument('-a', '--all', action='store_true', default=False) + tag_parser = subparsers.add_parser('tag', help='operate on the manifest tag') + tag_sub_parsers = tag_parser.add_subparsers(dest="tag_action") + + tag_remove_parser = tag_sub_parsers.add_parser('rm', help='Remove the SUIT Envelope tag if present') + tag_remove_parser.add_argument('-m', '--manifest', metavar='FILE', type=argparse.FileType('rb'), required=True) + tag_remove_parser.add_argument('-o', '--output-file', metavar='FILE', type=argparse.FileType('wb'), required=True) + + tag_add_parser = tag_sub_parsers.add_parser('add', help='Add the SUIT Envelope tag if not present') + tag_add_parser.add_argument('-m', '--manifest', metavar='FILE', type=argparse.FileType('rb'), required=True) + tag_add_parser.add_argument('-o', '--output-file', metavar='FILE', type=argparse.FileType('wb'), required=True) + return parser diff --git a/suit_tool/clidriver.py b/suit_tool/clidriver.py index 6c0149c..b07769b 100644 --- a/suit_tool/clidriver.py +++ b/suit_tool/clidriver.py @@ -20,7 +20,7 @@ import logging, sys from suit_tool.argparser import MainArgumentParser -from suit_tool import create, sign, parse, get_pubkey, keygen, sever #, verify, cert, init +from suit_tool import create, sign, parse, get_pubkey, keygen, sever, tag #, verify, cert, init # from suit_tool import update import colorama colorama.init() @@ -65,6 +65,7 @@ class CLIDriver(object): "sign": sign.main, "keygen": keygen.main, "sever" : sever.main, + "tag" : tag.main, }[self.options.action](self.options) or 0 sys.exit(rc) diff --git a/suit_tool/tag.py b/suit_tool/tag.py new file mode 100644 index 0000000..6b3c639 --- /dev/null +++ b/suit_tool/tag.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------------- +# Copyright 2022 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 cbor2 as cbor +from suit_tool.manifest import SUITEnvelopeTagged +envelope_tag_value = SUITEnvelopeTagged.fields['suit_envelope'].suit_key + +def rm(options, envelope): + if isinstance(envelope,cbor.CBORTag) and envelope.tag == envelope_tag_value: + return envelope.value + return envelope + + +def add(options, envelope): + if isinstance(envelope,cbor.CBORTag) and envelope.tag == envelope_tag_value: + return envelope + return cbor.CBORTag(envelope_tag_value, envelope) + +def main(options): + # Read the manifest wrapper + envelope = cbor.loads(options.manifest.read()) + if not hasattr(options, 'tag_action'): + return 1 + + new_envelope = { + 'rm' : rm, + 'add' : add + }.get(options.tag_action)(options, envelope) + + if not new_envelope: + return 1 + + options.output_file.write(cbor.dumps(new_envelope, canonical=True)) + + return 0 -- GitLab From 7653687e9296c5475abe838a88dffb9f0a6695c0 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Sun, 6 Nov 2022 12:14:08 +0000 Subject: [PATCH 2/3] Add manifest component-id --- examples/example8.json | 19 +++++++++++++++++++ suit_tool/compile.py | 4 ++++ suit_tool/manifest.py | 1 + 3 files changed, 24 insertions(+) create mode 100644 examples/example8.json diff --git a/examples/example8.json b/examples/example8.json new file mode 100644 index 0000000..85b7956 --- /dev/null +++ b/examples/example8.json @@ -0,0 +1,19 @@ +{ + "components" : [ + { + "install-id" : ["00"], + "bootable" : true, + "install-digest": { + "algorithm-id": "sha256", + "digest-bytes": "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210" + }, + "vendor-id" : "fa6b4a53-d5ad-5fdf-be9d-e663e4d41ffe", + "class-id" : "1492af14-2569-5e48-bf42-9b2d51f2ab45", + "install-size" : 34768 + } + ], + "manifest-version": 1, + "manifest-sequence-number": 0, + "manifest-component-id" : ["manifest"], + "reference-uri" : "http://suit.example/cid-example.suit" +} diff --git a/suit_tool/compile.py b/suit_tool/compile.py index 54458e4..9864489 100644 --- a/suit_tool/compile.py +++ b/suit_tool/compile.py @@ -407,6 +407,10 @@ def compile_manifest(options, m): 'manifest-sequence-number' : m['manifest-sequence-number'], 'common' : common.to_json() } + if 'manifest-component-id' in m: + jmanifest['manifest-component-id'] = m['manifest-component-id'] + if 'reference-uri' in m: + jmanifest['reference-uri'] = m['reference-uri'] # for k,v in {'deres':DepSeq, 'fetch': FetchSeq, 'install':InstSeq, 'validate':ValidateSeq, 'run':RunSeq, 'load':LoadSeq}.items(): # # print('sequence:{}'.format(k)) diff --git a/suit_tool/manifest.py b/suit_tool/manifest.py index 1fe16b9..9bb0768 100644 --- a/suit_tool/manifest.py +++ b/suit_tool/manifest.py @@ -783,6 +783,7 @@ class SUITManifest(SUITManifestDict): 'sequence' : ('manifest-sequence-number', 2, SUITPosInt), 'common' : ('common', 3, SUITBWrapField(SUITCommon)), 'refuri' : ('reference-uri', 4, SUITTStr), + 'component' : ('manifest-component-id', 5, SUITComponentId), 'validate' : ('validate', 7, SUITBWrapField(SUITSequenceComponentReset)), 'load' : ('load', 8, SUITBWrapField(SUITSequenceComponentReset)), -- GitLab From 3a1eeca4b6e0bb82e5a60236586c3f3c52883b51 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 3 Dec 2024 15:27:47 +0000 Subject: [PATCH 3/3] Update suit-tool code to latest CDDL --- .gitignore | 4 +- cddl/.gitignore | 2 + cddl/cose_cddl.py | 40 +++++++++++++++++++ cddl/suit-complete.py | 30 ++++++++++++++ cddl/suit_cddl.py | 29 ++++++++++++++ examples/.gitignore | 3 ++ examples/make-examples.sh | 5 +++ suit_tool/__init__.py | 2 +- suit_tool/compile.py | 30 ++++++++------ suit_tool/manifest.py | 84 +++++++++++++++++++++++++++------------ 10 files changed, 188 insertions(+), 41 deletions(-) mode change 100644 => 100755 .gitignore create mode 100644 cddl/.gitignore create mode 100755 cddl/cose_cddl.py create mode 100755 cddl/suit-complete.py create mode 100755 cddl/suit_cddl.py create mode 100644 examples/.gitignore mode change 100644 => 100755 examples/make-examples.sh mode change 100644 => 100755 suit_tool/__init__.py mode change 100644 => 100755 suit_tool/compile.py mode change 100644 => 100755 suit_tool/manifest.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index b3da4ec..43eeb54 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *__pycache__ *.pyc *.DS_Store +.vscode *.hex examples/*.cbor examples/*.json.txt -examples/*.suit \ No newline at end of file +examples/*.suit +build/* diff --git a/cddl/.gitignore b/cddl/.gitignore new file mode 100644 index 0000000..5be6db3 --- /dev/null +++ b/cddl/.gitignore @@ -0,0 +1,2 @@ +*.cddl +*.xml \ No newline at end of file diff --git a/cddl/cose_cddl.py b/cddl/cose_cddl.py new file mode 100755 index 0000000..f49acde --- /dev/null +++ b/cddl/cose_cddl.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------------- +# Copyright 2022 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 xml.etree.ElementTree as ET +import requests + +cose_source = 'https://www.ietf.org/archive/id/draft-ietf-cose-msg-24.xml' +cose_filename = 'cose.xml' +cose_cddl_filename = 'cose.cddl' + +with open(cose_filename, 'w') as fd: + r = requests.get(cose_source) + r.raise_for_status() + fd.write(r.text) + +# Load cose xml +xml_cose = ET.parse(cose_filename) +cddl_elements = xml_cose.findall("//artwork[@type='CDDL']") + +cddl = '\n'.join([e.text for e in cddl_elements]) + +with open(cose_cddl_filename, 'w') as fd: + fd.write(cddl) diff --git a/cddl/suit-complete.py b/cddl/suit-complete.py new file mode 100755 index 0000000..f92d349 --- /dev/null +++ b/cddl/suit-complete.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------------- +# Copyright 2022 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 cose_cddl +import suit_cddl + +suit_complete_cddl = 'suit-complete.cddl' + +with open(suit_complete_cddl, 'w') as sc_fd: + with open(suit_cddl.suit_cddl_filename) as s_fd: + sc_fd.write(s_fd.read()) + with open(cose_cddl.cose_cddl_filename) as c_fd: + sc_fd.write(c_fd.read()) diff --git a/cddl/suit_cddl.py b/cddl/suit_cddl.py new file mode 100755 index 0000000..90b8b01 --- /dev/null +++ b/cddl/suit_cddl.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ---------------------------------------------------------------------------- +# Copyright 2022 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 requests + +suit_source = 'https://raw.githubusercontent.com/suit-wg/manifest-spec/master/draft-ietf-suit-manifest.cddl' +suit_cddl_filename = 'suit.cddl' + +with open(suit_cddl_filename, 'w') as fd: + r = requests.get(suit_source) + r.raise_for_status() + fd.write(r.text) diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..18690f6 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,3 @@ +*.cbor +*.json.txt +*.suit diff --git a/examples/make-examples.sh b/examples/make-examples.sh old mode 100644 new mode 100755 index d01161a..1161aad --- a/examples/make-examples.sh +++ b/examples/make-examples.sh @@ -3,10 +3,13 @@ set -e set -x STOOL="python3 ../bin/suit-tool" SRCS=`ls *.json` +CDDLTOOL=cddl rm -f examples.txt for SRC in $SRCS ; do $STOOL create -i $SRC -o $SRC.suit + $CDDLTOOL ../cddl/suit-complete.cddl validate $SRC.suit $STOOL sign -m $SRC.suit -k ../private_key.pem -o signed-$SRC.suit + $CDDLTOOL ../cddl/suit-complete.cddl validate signed-$SRC.suit $STOOL parse -m signed-$SRC.suit > signed-$SRC.txt rm -f $SRC.txt @@ -19,6 +22,8 @@ for SRC in $SRCS ; do if python3 -c 'import json, sys; sys.exit(0 if json.load(open(sys.argv[1])).get("severable") else 1)' $SRC ; then $STOOL sever -a -m $SRC.suit -o severed-$SRC.suit + $CDDLTOOL ../cddl/suit-complete.cddl validate severed-$SRC.suit + echo "Total size of the Envelope without COSE authentication object or Severable Elements: " `stat -f "%z" severed-$SRC.suit` >> $SRC.txt echo "" >> $SRC.txt echo "Envelope:">> $SRC.txt diff --git a/suit_tool/__init__.py b/suit_tool/__init__.py old mode 100644 new mode 100755 index dd088ec..f1533d9 --- a/suit_tool/__init__.py +++ b/suit_tool/__init__.py @@ -16,4 +16,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = '0.0.2' +__version__ = '0.19.2' diff --git a/suit_tool/compile.py b/suit_tool/compile.py old mode 100644 new mode 100755 index 9864489..a6f3b08 --- a/suit_tool/compile.py +++ b/suit_tool/compile.py @@ -36,7 +36,7 @@ from cryptography.hazmat.primitives import hashes from suit_tool.manifest import SUITComponentId, SUITCommon, SUITSequence, \ suitCommonInfo, SUITCommand, SUITManifest, \ - SUITEnvelope, SUITTryEach, SUITBWrapField, SUITText, \ + SUITEnvelope, SUITTryEach, SUITBWrapField, \ SUITDigest, SUITDependencies, SUITDependency, SUITEnvelopeTagged \ import suit_tool.create @@ -396,7 +396,7 @@ def compile_manifest(options, m): # print('Common') common = SUITCommon().from_json({ 'components': [id.to_json() for id in ids.keys()], - 'common-sequence': CommonSeq.to_json(), + 'shared-sequence': CommonSeq.to_json(), }) if len(Dependencies.items): common.dependencies = Dependencies @@ -416,12 +416,18 @@ def compile_manifest(options, m): # # print('sequence:{}'.format(k)) # print(v.to_json()) + # Text has to be internationalized. The manifest defines multiple sections + # where each section has an internationalization string + # First get the map of descriptions at the top level. mtext = {} - for k in ['manifest-description', 'update-description']: - if k in m: - mtext[k] = m[k] + # Collect all the descriptions: + for lang, desc in m.get('description',{}).items(): + d = {k:desc[k] for k in ['manifest-description', 'update-description'] if k in desc} + if len(d): + mtext[lang] = d + + # Collect all the components with internationalization for c in m['components']: - ctext = {} cfields = [ 'vendor-name', 'model-name', @@ -431,13 +437,11 @@ def compile_manifest(options, m): 'component-version', 'version-required', ] - for k in cfields: - if k in c: - ctext[k] = c[k] - if len(ctext): - cid = SUITComponentId().from_json(c['install-id']).to_suit() - mtext[cid] = ctext - + cid = SUITComponentId().from_json(c['install-id']).to_suit() + for lang, desc in c.get('description',{}).items(): + if not lang in mtext: + mtext[lang]={} + mtext[lang].update({cid: {k:desc[k] for k in cfields if k in desc}}) jmanifest.update({k:v for k,v in { 'dependency-resolution' : DepSeq.to_json(), 'payload-fetch' : FetchSeq.to_json(), diff --git a/suit_tool/manifest.py b/suit_tool/manifest.py old mode 100644 new mode 100755 index 9bb0768..3e85589 --- a/suit_tool/manifest.py +++ b/suit_tool/manifest.py @@ -100,7 +100,6 @@ class SUITPosInt(SUITInt): def from_json(self, v): TreeBranch.append(type(self)) _v = int(v) - # print (_v) if _v < 0: raise SUITException( m = 'Positive Integers must be >= 0. Got {}.'.format(_v), @@ -164,15 +163,16 @@ class SUITManifestDict: sd[f.suit_key] = v.to_suit() return sd def to_debug(self, indent): - s = '{' newindent = indent + one_indent + rows = [] for k, f in self.fields.items(): v = getattr(self, k) if v: - s += '\n{ind}/ {jk} / {sk}:'.format(ind=newindent, jk=f.json_key, sk=f.suit_key) - s += v.to_debug(newindent) + ',' - s += '\n' + indent + '}' + rows.append('{ind}/ {jk} / {sk}:{v}'.format( + ind=newindent, jk=f.json_key, sk=f.suit_key, v=v.to_debug(newindent) + )) + s = '{\n' + ',\n'.join(rows) + '\n' + indent + '}' return s @@ -314,7 +314,7 @@ class SUITManifestArray: def to_debug(self, indent): newindent = indent + one_indent s = '[\n' - s += ' ,\n'.join([newindent + v.to_debug(newindent) for v in self.items]) + s += ',\n'.join([newindent + v.to_debug(newindent) for v in self.items]) s += '\n' + indent + ']' return s class SUITBytes: @@ -372,7 +372,7 @@ class SUITNil: raise Exception('Expected Nil') return self def to_debug(self, indent): - return 'F6 / nil /' + return 'null / nil /' class SUITTStr(SUITRaw): def from_json(self, d): @@ -710,7 +710,7 @@ class SUITCommon(SUITManifestDict): fields = SUITManifestNamedList.mkfields({ 'dependencies' : ('dependencies', 1, SUITDependencies), 'components' : ('components', 2, SUITComponents), - 'common_sequence' : ('common-sequence', 4, SUITBWrapField(SUITSequenceComponentReset)), + 'shared_sequence' : ('shared-sequence', 4, SUITBWrapField(SUITSequenceComponentReset)), }) class SUITComponentText(SUITManifestDict): @@ -731,7 +731,9 @@ class SUITText(SUITManifestDict): 'json' : ('json-source', 3, SUITTStr), 'yaml' : ('yaml-source', 4, SUITTStr), }) - components={} + def __init__(self): + super(SUITText,self).__init__() + self.components = {} def to_json(self): d = super(SUITText, self).to_json() @@ -757,26 +759,59 @@ class SUITText(SUITManifestDict): if not isinstance(v, str): self.components[SUITComponentId().from_suit(k)] = SUITComponentText().from_suit(v) # Treat everything else as a normal manifestDict - return super(SUITText, self).from_json(data) + return super(SUITText, self).from_suit(data) def to_debug(self, indent): - s = '{' newindent = indent + one_indent - + rows = [] for k, f in self.fields.items(): v = getattr(self, k) if v: - s += '\n{ind}/ {jk} / {sk}:'.format(ind=newindent, jk=f.json_key, sk=f.suit_key) - s += v.to_debug(newindent) + ',' + s = '{ind}/ {jk} / {sk}:'.format(ind=newindent, jk=f.json_key, sk=f.suit_key) + s += v.to_debug(newindent) + rows.append(s) for k, f in self.components.items(): - s += '\n' + newindent + '{}:'.format(k.to_debug(newindent + one_indent)) + s = '\n' + newindent + '{}:'.format(k.to_debug(newindent + one_indent)) s += f.to_debug(newindent + one_indent) + rows.append(s) + s = '{\n' + ',\n'.join(rows) + '\n' + indent + '}' + return s +class SUITInternationalText(SUITManifestDict): + def __init__(self): + super(SUITInternationalText,self).__init__() + self.languages = {} + def to_json(self): + return { + lang : desc.to_json() for lang,desc in self.languages.items() + } + def from_json(self, data): + for lang,desc in data.items(): + self.languages[lang] = SUITText().from_json(desc) + return self + def to_suit(self): + return { + lang : desc.to_suit() for lang,desc in self.languages.items() + } + def from_suit(self, data): + print(f'{type(self)}.from_suit({data})') + for lang, desc in data.items(): + print(f'decoding from suit: {lang}:{desc} =>') + d = SUITText().from_suit(desc) + print(f'{d}') + self.languages[lang] = d + print(self.languages) + return self + def to_debug(self, indent): + s = '{' + newindent = indent + one_indent + for lang, desc in self.languages.items(): + s += f'\n{newindent}\'{lang}\' : {desc.to_debug(newindent+one_indent)},' + if len(self.languages): + s = s[:-1] s += '\n' + indent + '}' - return s - - + class SUITManifest(SUITManifestDict): fields = SUITManifestDict.mkfields({ 'version' : ('manifest-version', 1, SUITPosInt), @@ -791,9 +826,9 @@ class SUITManifest(SUITManifestDict): 'deres' : ('dependency-resolution', 15, SUITMakeSeverableField(SUITSequenceComponentReset)), 'fetch' : ('payload-fetch', 16, SUITMakeSeverableField(SUITSequenceComponentReset)), - 'install' : ('install', 17, SUITMakeSeverableField(SUITSequenceComponentReset)), + 'install' : ('install', 20, SUITMakeSeverableField(SUITSequenceComponentReset)), - 'text' : ('text', 23, SUITMakeSeverableField(SUITText)), + 'text' : ('text', 23, SUITMakeSeverableField(SUITInternationalText)), 'coswid' : ('coswid', 24, SUITBytes), }) @@ -894,10 +929,7 @@ class COSEList(SUITManifestArray): if len(self.items): s += ',\n' # show signatures - # s += indent1 + 'signatures: [\n' - indent2 = indent1 - s += ' ,\n'.join([indent2 + '/ signature: / ' + v.to_debug(indent2) for v in self.items]) - s += '\n' + indent1 + ']' + s += ' ,\n'.join([indent1 + '/ signature: / ' + v.to_debug(indent) for v in self.items]) s += '\n{}]'.format(indent) return s @@ -912,9 +944,9 @@ class SUITEnvelope(SUITManifestDict): 'deres': ('dependency-resolution', 15, SUITBWrapField(SUITSequence)), 'fetch': ('payload-fetch', 16, SUITBWrapField(SUITSequence)), - 'install': ('install', 17, SUITBWrapField(SUITSequence)), + 'install': ('install', 20, SUITBWrapField(SUITSequence)), - 'text': ('text', 23, SUITBWrapField(SUITText)), + 'text': ('text', 23, SUITBWrapField(SUITInternationalText)), 'coswid': ('coswid', 14, SUITBytes), }) severable_fields = {'deres', 'fetch', 'install', 'text', 'coswid'} -- GitLab