1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/dist/tools/suit_v3/suit-manifest-generator/suit_tool/manifest.py
Koen Zandberg 009a317b14
dist/tools/suit_v3: Add ietf-v3 manifest generator
Co-authored-by: Kaspar Schleiser <kaspar@schleiser.de>
2020-03-18 14:13:12 +01:00

689 lines
23 KiB
Python

#!/usr/bin/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 collections
import binascii
import cbor
import copy
import uuid
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
ManifestKey = collections.namedtuple(
'ManifestKey',
[
'json_key',
'suit_key',
'obj'
]
)
def to_bytes(s):
try:
return binascii.a2b_hex(s)
except:
try:
return binascii.a2b_base64(s)
except:
if isinstance(s,str):
return s.encode('utf-8')
elif isinstance(s,bytes):
return s
else:
return str(s).encode('utf-8')
class SUITCommonInformation:
def __init__(self):
self.component_ids = []
self.current_index = 0
self.indent_size = 4
def component_id_to_index(self, cid):
id = -1
for i, c in enumerate(self.component_ids):
if c == cid and i >= 0:
id = i
return id
suitCommonInfo = SUITCommonInformation()
one_indent = ' '
class SUITInt:
def from_json(self, v):
self.v = int(v)
return self
def to_json(self):
return self.v
def from_suit(self, v):
self.v = int(v)
return self
def to_suit(self):
return self.v
def to_debug(self, indent):
return str(self.v)
class SUITPosInt(SUITInt):
def from_json(self, v):
_v = int(v)
if _v < 0:
raise Exception('Positive Integers must be >= 0')
self.v = _v
return self
def from_suit(self, v):
return self.from_json(v)
class SUITManifestDict:
def mkfields(d):
# rd = {}
return {k: ManifestKey(*v) for k,v in d.items()}
def __init__(self):
pass
def from_json(self, data):
for k, f in self.fields.items():
v = data.get(f.json_key, None)
setattr(self, k, f.obj().from_json(v) if v is not None else None)
return self
def to_json(self):
j = {}
for k, f in self.fields.items():
v = getattr(self, k)
if v:
j[f.json_key] = v.to_json()
return j
def from_suit(self, data):
for k, f in self.fields.items():
v = data.get(f.suit_key, None)
d = f.obj().from_suit(v) if v is not None else None
setattr(self, k, d)
return self
def to_suit(self):
sd = {}
for k, f in self.fields.items():
v = getattr(self, k)
if v:
sd[f.suit_key] = v.to_suit()
return sd
def to_debug(self, indent):
s = '{'
newindent = indent + one_indent
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 + '}'
return s
class SUITManifestNamedList(SUITManifestDict):
def from_suit(self, data):
for k, f in self.fields.items():
setattr(self, k, f.obj().from_suit(data[f.suit_key]))
return self
def to_suit(self):
sd = [None] * (max([f.suit_key for k, f in self.fields.items()]) + 1)
for k, f in self.fields.items():
v = getattr(self, k)
if v:
sd[f.suit_key] = v.to_suit()
return sd
def to_debug(self, indent):
newindent = indent + one_indent
items = []
for k, f in self.fields.items():
v = getattr(self, k)
if v:
items.append('/ ' + f.json_key + ' / ' + v.to_debug(newindent))
s = '[\n{newindent}{items}\n{indent}]'.format(
newindent=newindent,
indent=indent,
items=',\n{newindent}'.format(newindent=newindent).join(items)
)
return s
class SUITKeyMap:
def mkKeyMaps(m):
return {v:k for k,v in m.items()}, m
def to_json(self):
return self.rkeymap[self.v]
def from_json(self, d):
self.v = self.keymap[d]
return self
def to_suit(self):
return self.v
def from_suit(self, d):
self.v = self.keymap[self.rkeymap[d]]
return self
def to_debug(self, indent):
s = str(self.v) + ' / ' + self.to_json() + ' /'
return s
def SUITBWrapField(c):
class SUITBWrapper:
def to_suit(self):
return cbor.dumps(self.v.to_suit(), sort_keys=True)
def from_suit(self, d):
self.v = c().from_suit(cbor.loads(d))
return self
def to_json(self):
return self.v.to_json()
def from_json(self, d):
self.v = c().from_json(d)
return self
def to_debug(self, indent):
s = 'h\''
s += binascii.b2a_hex(self.to_suit()).decode('utf-8')
s += '\' / '
s += self.v.to_debug(indent)
s += ' /'
return s
return SUITBWrapper
class SUITManifestArray:
def __init__(self):
self.items=[]
def __eq__(self, rhs):
if len(self.items) != len(rhs.items):
return False
for a,b in zip(self.items, rhs.items):
if not a == b:
return False
return True
def from_json(self, data):
self.items = []
for d in data:
self.items.append(self.field.obj().from_json(d))
return self
def to_json(self):
j = []
for i in self.items:
j.append(i.to_json())
return j
def from_suit(self, data):
self.items = []
for d in data:
self.items.append(self.field.obj().from_suit(d))
return self
def to_suit(self):
l = []
for i in self.items:
l.append(i.to_suit())
return l
def append(self, element):
if not isinstance(element, self.field.obj):
raise Exception('element {} is not a {}'.format(element, self.field.obj))
self.items.append(element)
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' + indent + ']'
return s
class SUITBytes:
def to_json(self):
return binascii.b2a_hex(self.v).decode('utf-8')
def from_json(self, d):
self.v = to_bytes(d)
return self
def from_suit(self, d):
self.v = bytes(d)
return self
def to_suit(self):
return self.v
def to_debug(self, indent):
return 'h\'' + self.to_json() + '\''
def __eq__(self, rhs):
return self.v == rhs.v
class SUITUUID(SUITBytes):
def from_json(self, d):
self.v = uuid.UUID(d).bytes
return self
def from_suit(self, d):
self.v = uuid.UUID(bytes=d).bytes
return self
def to_debug(self, indent):
return 'h\'' + self.to_json() + '\' / ' + str(uuid.UUID(bytes=self.v)) + ' /'
class SUITRaw:
def to_json(self):
return self.v
def from_json(self, d):
self.v = d
return self
def to_suit(self):
return self.v
def from_suit(self, d):
self.v = d
return self
def to_debug(self, indent):
return str(self.v)
class SUITNil:
def to_json(self):
return None
def from_json(self, d):
if d is not None:
raise Exception('Expected Nil')
return self
def to_suit(self):
return None
def from_suit(self, d):
if d is not None:
raise Exception('Expected Nil')
return self
def to_debug(self, indent):
return 'F6 / nil /'
class SUITTStr(SUITRaw):
def from_json(self, d):
self.v = str(d)
return self
def from_suit(self, d):
self.v = str(d)
return self
def to_debug(self, indent):
return '\''+ str(self.v) + '\''
class SUITComponentId(SUITManifestArray):
field = collections.namedtuple('ArrayElement', 'obj')(obj=SUITBytes)
def to_debug(self, indent):
newindent = indent + one_indent
s = '[' + ''.join([v.to_debug(newindent) for v in self.items]) + ']'
return s
def __hash__(self):
return hash(tuple([i.v for i in self.items]))
class SUITComponentIndex(SUITComponentId):
def to_suit(self):
return suitCommonInfo.component_id_to_index(self)
def from_suit(self, d):
return super(SUITComponentIndex, self).from_suit(
suitCommonInfo.component_ids[d].to_suit()
)
def to_debug(self, indent):
newindent = indent + one_indent
s = '{suit} / [{dbg}] /'.format(
suit=self.to_suit(),
dbg=''.join([v.to_debug(newindent) for v in self.items])
)
return s
class SUITComponents(SUITManifestArray):
field = collections.namedtuple('ArrayElement', 'obj')(obj=SUITComponentId)
def from_suit(self, data):
super(SUITComponents, self).from_suit(data)
suitCommonInfo.component_ids = self.items
return self
def from_json(self, j):
super(SUITComponents, self).from_json(j)
suitCommonInfo.component_ids = self.items
return self
class SUITDigestAlgo(SUITKeyMap):
rkeymap, keymap = SUITKeyMap.mkKeyMaps({
'sha224' : 1,
'sha256' : 2,
'sha384' : 3,
'sha512' : 4
})
class SUITDigest(SUITManifestNamedList):
fields = SUITManifestNamedList.mkfields({
'algo' : ('algorithm-id', 0, SUITDigestAlgo),
'digest' : ('digest-bytes', 1, SUITBytes)
})
class SUITCompressionInfo(SUITKeyMap):
rkeymap, keymap = SUITKeyMap.mkKeyMaps({
'gzip' : 1,
'bzip2' : 2,
'deflate' : 3,
'lz4' : 4,
'lzma' : 7
})
class SUITParameters(SUITManifestDict):
fields = SUITManifestDict.mkfields({
'digest' : ('image-digest', 11, SUITDigest),
'size' : ('image-size', 12, SUITPosInt),
'uri' : ('uri', 6, SUITTStr),
'src' : ('source-component', 10, SUITComponentIndex),
'compress' : ('compression-info', 8, SUITCompressionInfo)
})
def from_json(self, j):
return super(SUITParameters, self).from_json(j)
class SUITTryEach(SUITManifestArray):
pass
def SUITCommandContainer(jkey, skey, argtype):
class SUITCmd(SUITCommand):
json_key = jkey
suit_key = skey
def __init__(self):
pass
def to_suit(self):
return [self.suit_key, self.arg.to_suit()]
def to_json(self):
if self.json_key == 'directive-set-component-index':
return {}
else:
return {
'command-id' : self.json_key,
'command-arg' : self.arg.to_json(),
'component-id' : self.cid.to_json()
}
def from_json(self, j):
if j['command-id'] != self.json_key:
raise Except('JSON Key mismatch error')
if self.json_key != 'directive-set-component-index':
self.cid = SUITComponentId().from_json(j['component-id'])
self.arg = argtype().from_json(j['command-arg'])
return self
def from_suit(self, s):
if s[0] != self.suit_key:
raise Except('SUIT Key mismatch error')
if self.json_key == 'directive-set-component-index':
suitCommonInfo.current_index = s[1]
else:
self.cid = suitCommonInfo.component_ids[suitCommonInfo.current_index]
self.arg = argtype().from_suit(s[1])
return self
def to_debug(self, indent):
s = '/ {} / {},'.format(self.json_key, self.suit_key)
s += self.arg.to_debug(indent)
return s
return SUITCmd
class SUITCommand:
def from_json(self, j):
return self.jcommands[j['command-id']]().from_json(j)
def from_suit(self, s):
return self.scommands[s[0]]().from_suit(s)
SUITCommand.commands = [
SUITCommandContainer('condition-vendor-identifier', 1, SUITUUID),
SUITCommandContainer('condition-class-identifier', 2, SUITUUID),
SUITCommandContainer('condition-image-match', 3, SUITNil),
SUITCommandContainer('condition-use-before', 4, SUITRaw),
SUITCommandContainer('condition-component-offset', 5, SUITRaw),
SUITCommandContainer('condition-custom', 6, SUITRaw),
SUITCommandContainer('condition-device-identifier', 24, SUITRaw),
SUITCommandContainer('condition-image-not-match', 25, SUITRaw),
SUITCommandContainer('condition-minimum-battery', 26, SUITRaw),
SUITCommandContainer('condition-update-authorised', 27, SUITRaw),
SUITCommandContainer('condition-version', 28, SUITRaw),
SUITCommandContainer('directive-set-component-index', 12, SUITPosInt),
SUITCommandContainer('directive-set-dependency-index', 13, SUITRaw),
SUITCommandContainer('directive-abort', 14, SUITRaw),
SUITCommandContainer('directive-try-each', 15, SUITTryEach),
SUITCommandContainer('directive-process-dependency', 18, SUITRaw),
SUITCommandContainer('directive-set-parameters', 19, SUITParameters),
SUITCommandContainer('directive-override-parameters', 20, SUITParameters),
SUITCommandContainer('directive-fetch', 21, SUITNil),
SUITCommandContainer('directive-copy', 22, SUITRaw),
SUITCommandContainer('directive-run', 23, SUITRaw),
SUITCommandContainer('directive-wait', 29, SUITRaw),
SUITCommandContainer('directive-run-sequence', 30, SUITRaw),
SUITCommandContainer('directive-run-with-arguments', 31, SUITRaw),
SUITCommandContainer('directive-swap', 32, SUITRaw),
]
SUITCommand.jcommands = { c.json_key : c for c in SUITCommand.commands}
SUITCommand.scommands = { c.suit_key : c for c in SUITCommand.commands}
class SUITSequence(SUITManifestArray):
field = collections.namedtuple('ArrayElement', 'obj')(obj=SUITCommand)
def to_suit(self):
suit_l = []
suitCommonInfo.current_index = 0 if len(suitCommonInfo.component_ids) == 1 else None
for i in self.items:
if i.json_key == 'directive-set-component-index':
suitCommonInfo.current_index = i.arg.v
else:
cidx = suitCommonInfo.component_id_to_index(i.cid)
if cidx != suitCommonInfo.current_index:
# Change component
cswitch = SUITCommand().from_json({
'command-id' : 'directive-set-component-index',
'command-arg' : cidx
})
suitCommonInfo.current_index = cidx
suit_l += cswitch.to_suit()
suit_l += i.to_suit()
return suit_l
def to_debug(self, indent):
return super(SUITSequence, SUITSequence().from_suit(self.to_suit())).to_debug(indent)
def from_suit(self, s):
self.items = [SUITCommand().from_suit(i) for i in zip(*[iter(s)]*2)]
return self
SUITTryEach.field = collections.namedtuple('ArrayElement', 'obj')(obj=SUITSequence)
class SUITSequenceComponentReset(SUITSequence):
def to_suit(self):
suitCommonInfo.current_index = None
return super(SUITSequenceComponentReset, self).to_suit()
def SUITMakeSeverableField(c):
class SUITSeverableField:
objtype = SUITBWrapField(c)
def from_json(self, data):
if 'algorithm-id' in data:
self.v = SUITDigest().from_json(data)
else:
self.v = self.objtype().from_json(data)
return self
def from_suit(self, data):
if isinstance(data, list):
self.v = SUITDigest().from_suit(data)
else:
self.v = self.objtype().from_suit(data)
return self
def to_json(self):
return self.v.to_json()
def to_suit(self):
return self.v.to_suit()
def to_debug(self, indent):
return self.v.to_debug(indent)
return SUITSeverableField
# class SUITSequenceOrDigest()
class SUITCommon(SUITManifestDict):
fields = SUITManifestNamedList.mkfields({
# 'dependencies' : ('dependencies', 1, SUITBWrapField(SUITDependencies)),
'components' : ('components', 2, SUITBWrapField(SUITComponents)),
# 'dependency_components' : ('dependency-components', 3, SUITBWrapField(SUITDependencies)),
'common_sequence' : ('common-sequence', 4, SUITBWrapField(SUITSequenceComponentReset)),
})
class SUITManifest(SUITManifestDict):
fields = SUITManifestDict.mkfields({
'version' : ('manifest-version', 1, SUITPosInt),
'sequence' : ('manifest-sequence-number', 2, SUITPosInt),
'common' : ('common', 3, SUITBWrapField(SUITCommon)),
'deres' : ('dependency-resolution', 7, SUITMakeSeverableField(SUITSequenceComponentReset)),
'fetch' : ('payload-fetch', 8, SUITMakeSeverableField(SUITSequenceComponentReset)),
'install' : ('install', 9, SUITMakeSeverableField(SUITSequenceComponentReset)),
'validate' : ('validate', 10, SUITBWrapField(SUITSequenceComponentReset)),
'load' : ('load', 11, SUITBWrapField(SUITSequenceComponentReset)),
'run' : ('run', 12, SUITBWrapField(SUITSequenceComponentReset)),
})
class COSE_Algorithms(SUITKeyMap):
rkeymap, keymap = SUITKeyMap.mkKeyMaps({
'ES256' : -7,
'ES384' : -35,
'ES512' : -36,
'EdDSA' : -8,
'HSS-LMS' : -46,
})
class COSE_CritList(SUITManifestArray):
field = collections.namedtuple('ArrayElement', 'obj')(obj=SUITInt)
class COSE_header_map(SUITManifestDict):
fields = SUITManifestDict.mkfields({
# 1: algorithm Identifier
'alg' : ('alg', 1, COSE_Algorithms),
# 2: list of critical headers (criticality)
# 3: content type
# 4: key id
'kid' : ('kid', 4, SUITBytes),
# 5: IV
# 6: partial IV
# 7: counter signature(s)
})
class COSE_Sign:
pass
class COSE_Sign1(SUITManifestNamedList):
fields = SUITManifestDict.mkfields({
'protected' : ('protected', 0, SUITBWrapField(COSE_header_map)),
'unprotected' : ('unprotected', 1, COSE_header_map),
'payload' : ('payload', 2, SUITBWrapField(SUITDigest)),
'signature' : ('signature', 3, SUITBytes)
})
class COSE_Mac:
pass
class COSE_Mac0:
pass
class COSETagChoice(SUITManifestDict):
def to_suit(self):
for k, f in self.fields.items():
v = getattr(self, k, None)
if v:
return cbor.Tag(tag=f.suit_key, value=v.to_suit())
return None
def from_suit(self, data):
for k, f in self.fields.items():
if data.tag == f.suit_key:
v = data.value
d = f.obj().from_suit(v) if v is not None else None
setattr(self, k, d)
return self
def to_debug(self, indent):
s = ''
for k, f in self.fields.items():
if hasattr(self, k):
v = getattr(self, k)
newindent = indent + one_indent
s = '{tag}({value})'.format(tag=f.suit_key, value=v.to_debug(newindent))
return s
class COSETaggedAuth(COSETagChoice):
fields = SUITManifestDict.mkfields({
'cose_sign' : ('COSE_Sign_Tagged', 98, COSE_Sign),
'cose_sign1' : ('COSE_Sign1_Tagged', 18, COSE_Sign1),
'cose_mac' : ('COSE_Mac_Tagged', 97, COSE_Mac),
'cose_mac0' : ('COSE_Mac0_Tagged', 17, COSE_Mac0)
})
class COSEList(SUITManifestArray):
field = collections.namedtuple('ArrayElement', 'obj')(obj=COSETaggedAuth)
def from_suit(self, data):
return super(COSEList, self).from_suit(data)
class SUITWrapper(SUITManifestDict):
fields = SUITManifestDict.mkfields({
'auth' : ('authentication-wrapper', 2, SUITBWrapField(COSEList)),
'manifest' : ('manifest', 3, SUITBWrapField(SUITManifest)),
'deres': ('dependency-resolution', 7, SUITBWrapField(SUITSequence)),
'fetch': ('payload-fetch', 8, SUITBWrapField(SUITSequence)),
'install': ('install', 9, SUITBWrapField(SUITSequence)),
'validate': ('validate', 10, SUITBWrapField(SUITSequence)),
'load': ('load', 11, SUITBWrapField(SUITSequence)),
'run': ('run', 12, SUITBWrapField(SUITSequence)),
# 'text': ('text', 13, SUITBWrapField(SUITSequence)),
})
severable_fields = {'deres', 'fetch', 'install'} #, 'text'}
digest_algorithms = {
'sha224' : hashes.SHA224,
'sha256' : hashes.SHA256,
'sha384' : hashes.SHA384,
'sha512' : hashes.SHA512
}
def to_severable(self, digest_alg):
sev = copy.deepcopy(self)
for k in sev.severable_fields:
f = sev.manifest.v.fields[k]
if not hasattr(sev.manifest.v, k):
continue
v = getattr(sev.manifest.v, k)
if v is None:
continue
cbor_field = cbor.dumps(v.to_suit(), sort_keys=True)
digest = hashes.Hash(digest_algorithms.get(digest_alg)(), backend=default_backend())
digest.update(cbor_field)
field_digest = SUITDigest().from_json({
'algorithm-id' : digest_alg,
'digest-bytes' : digest.finalize()
})
cbor_digest = cbor.dumps(field_digest.to_suit(), sort_keys=True)
if len(cbor_digest) < len(cbor_field):
setattr(sev.manifest.v, k, field_digest)
setattr(sev,k,v)
return sev
def from_severable(self):
raise Exception('From Severable unimplemented')
nsev = copy.deepcopy(self)
for k in nsev.severable_fields:
f = nsev.fields[k]
if not hasattr(nsev, k):
continue
v = getattr(nsev, k)
if v is None:
continue
# Verify digest
cbor_field = cbor.dumps(v.to_suit(), sort_keys=True)
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(cbor_field)
actual_digest = digest.finalize()
field_digest = getattr(sev.nsev.v, k)
expected_digest = field_digest.to_suit()[1]
if digest != expected_digest:
raise Exception('Field Digest mismatch: For {}, expected: {}, got {}'.format(
f.json_key, expected_digest, actual_digest
))
setattr(nsev.manifest.v, k, v)
delattr(nsev, k)
return nsev