Skip to content

Commit

Permalink
dist/tools/suit: Update suit tooling to IETF-v7 compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
bergzand committed Jul 3, 2020
1 parent 65c3b5e commit ee318ee
Show file tree
Hide file tree
Showing 21 changed files with 406 additions and 166 deletions.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ The `suit-tool` supports three sub-commands:
* `create` generates a new manifest.
* `sign` signs a manifest.
* `parse` parses an existing manifest into cbor-debug or a json representation.
* `keygen` Create a signing key. Not for production use.
* `pubkey` Get the public key for a supplied private key in uECC-compatible C definition.

The `suit-tool` has a configurable log level, specified with `-l`:

Expand Down Expand Up @@ -178,7 +180,7 @@ To add a component to the manifest from the command-line, use the following synt

The supported fields are:

* `file` the path to a file to use as a payload file.
* `file` the path fo a file to use as a payload file.
* `inst` the `install-id`.
* `uri` the URI where the file will be found.

Expand Down Expand Up @@ -207,3 +209,28 @@ suit-tool parse -m MANIFEST
```

If a json-representation is needed, add the '-j' flag.

## Keygen

Create an asymmetric keypair for non-production use. Production systems should use closely guarded keys, such as keys stored in an HSM.

```sh
suit-tool keygen [-t TYPE] -o KEYFILE
```

`suit-tool keygen` defaults to creating SECP256r1 keys. To create another type of key, use `-t`followed by one of:

* `secp256r1`
* `secp384r1`
* `secp521r1`
* `ed25519`

## UECC public key

Derive a public key in the format used by micro ECC. The input is a PEM private key.

```sh
suit-tool pubkey -k FILE
```

The tool will then print the public key in micro ECC format.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright 2016-2019 ARM Limited or its affiliates
# Copyright 2016-2020 ARM Limited or its affiliates
#
# SPDX-License-Identifier: Apache-2.0
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
install_requires = [
'cbor>=1.0.0',
'colorama>=0.4.0',
'cryptography>=2.8'
'cryptography>=2.8',
'pyhsslms>=1.0.0',
],
classifiers = [
"Programming Language :: Python :: 3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright 2016-2019 ARM Limited or its affiliates
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright 2019-2020 ARM Limited or its affiliates
Expand All @@ -17,22 +16,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------
import sys
import argparse
import sys, argparse, os
from suit_tool import __version__
from suit_tool import keygen
from suit_tool import get_pubkey
import json
import re


def str_to_component(s):
types = {
'file' : ('file', lambda x : str(x.strip('"'))),
# 'desc' : ('component-description', lambda x : str(x.strip('"'))),
'inst' : ('install-id', lambda x : [ str(y) for y in eval(x) ]),
'uri' : ('uri', lambda x : str(x.strip('"')))
}
d = {types[k][0]:types[k][1](v) for k,v in [ re.split(r'=',e, maxsplit=1) for e in re.split(r''',\s*(?=["']?[a-zA-Z0-9_-]+["']?=)''', s)]}
return d


class MainArgumentParser(object):

def __init__(self):
Expand All @@ -54,7 +54,7 @@ def _make_parser(self):

# create_parser.add_argument('-v', '--manifest-version', choices=['1'], default='1')
create_parser.add_argument('-i', '--input-file', metavar='FILE', type=argparse.FileType('r'),
help='An input file describing the update. The file must be formatted as JSON. The overal structure is described in README.')
help='An input file describing the update. The file must be formated as JSON. The overal structure is described in README.')
create_parser.add_argument('-o', '--output-file', metavar='FILE', type=argparse.FileType('wb'), required=True)
create_parser.add_argument('-f', '--format', metavar='FMT', choices=['suit', 'suit-debug', 'json'], default='suit')
create_parser.add_argument('-s', '--severable', action='store_true', help='Convert large elements to severable fields.')
Expand All @@ -72,9 +72,19 @@ def _make_parser(self):
parse_parser.add_argument('-m', '--manifest', metavar='FILE', type=argparse.FileType('rb'), required=True)
parse_parser.add_argument('-j', '--json-output', default=False, action='store_true', dest='json')

get_uecc_pubkey_parser = subparsers.add_parser('pubkey', help='Get the public key for a supplied private key in uECC-compatible C definition.')
get_pubkey_parser = subparsers.add_parser('pubkey', help='Get the public key for a supplied private key.')

get_pubkey_parser.add_argument('-k', '--private-key', metavar='FILE', type=argparse.FileType('rb'), required=True)
get_pubkey_parser.add_argument('-f', '--output-format', choices=get_pubkey.OutputFormaters.keys(), default='pem')
get_pubkey_parser.add_argument('-o', '--output-file', metavar='FILE', type=argparse.FileType('wb'), default=sys.stdout)

keygen_parser = subparsers.add_parser('keygen', help='Create a signing key. Not for production use')

get_uecc_pubkey_parser.add_argument('-k', '--private-key', metavar='FILE', type=argparse.FileType('rb'), required=True)
keygen_parser.add_argument('-t', '--type', choices=keygen.KeyGenerators.keys(),
default='secp256r1', help='The type of the key to generate')
keygen_parser.add_argument('-o', '--output-file', metavar='FILE', type=argparse.FileType('wb'), default=sys.stdout)
keygen_parser.add_argument('-f', '--output-format', choices=keygen.OutputFormaters.keys(), default='pem')
keygen_parser.add_argument('-l', '--levels', help='The number of hss-lms levels', type=int, default=2)

return parser

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright 2018-2020 ARM Limited or its affiliates
Expand All @@ -17,21 +17,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------
import logging
import sys
import logging, sys

from suit_tool.argparser import MainArgumentParser
from suit_tool import create, sign, parse, get_uecc_pubkey
from suit_tool import create, sign, parse, get_pubkey, keygen #, verify, cert, init

LOG = logging.getLogger(__name__)
LOG_FORMAT = '[%(levelname)s] %(asctime)s - %(name)s - %(message)s'

LOG = logging.getLogger(__name__)
LOG_FORMAT='[%(levelname)s] %(asctime)s - %(name)s - %(message)s'

def main():
driver = CLIDriver()
return driver.main()


class CLIDriver(object):

def __init__(self):
Expand All @@ -46,6 +44,10 @@ def __init__(self):
logging.basicConfig(level=log_level,
format=LOG_FORMAT,
datefmt='%Y-%m-%d %H:%M:%S')
logging.addLevelName( logging.INFO, "\033[1;32m%s\033[1;0m" % logging.getLevelName(logging.INFO))
logging.addLevelName( logging.WARNING, "\033[1;93m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
logging.addLevelName( logging.CRITICAL, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.CRITICAL))

LOG.debug('CLIDriver created. Arguments parsed and logging setup.')

def main(self):
Expand All @@ -56,8 +58,9 @@ def main(self):
# "cert": cert.main,
# "init": init.main,
# "update" : update.main,
"pubkey": get_uecc_pubkey.main,
"sign": sign.main
"pubkey": get_pubkey.main,
"sign": sign.main,
"keygen": keygen.main,
}[self.options.action](self.options) or 0

sys.exit(rc)
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright 2019 ARM Limited or its affiliates
# Copyright 2019-2020 ARM Limited or its affiliates
#
# SPDX-License-Identifier: Apache-2.0
#
Expand All @@ -19,16 +18,24 @@
# ----------------------------------------------------------------------------
import binascii
import copy
import collections
import json
import cbor
import sys
import textwrap
import itertools

import logging

from collections import OrderedDict

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes


from suit_tool.manifest import SUITComponentId, SUITCommon, SUITSequence, \
SUITCommand, \
SUITWrapper, SUITTryEach
suitCommonInfo, SUITCommand, SUITManifest, \
SUITWrapper, SUITTryEach, SUITBWrapField

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -92,16 +99,30 @@ def make_sequence(cid, choices, seq, params, cmds, pcid_key=None, param_drctv='d
seq.append(mkCommand(pcid, param_drctv, params))
TryEachCmd = SUITTryEach()
for c in choices:
TECseq = SUITSequence()
for item, cmd in neqcmds.items():
TECseq.append(cmd(cid, c))
TECseq = TryEachCmd.field.obj().from_json([])
params = {}
for param, pcmd in neqparams.items():
k,v = pcmd(cid, c)
params[k] = v
dep_params = {}
TECseq_cmds = []
for item, cmd in neqcmds.items():
ncmd = cmd(cid, c)
for dp in ncmd.dep_params:
if dp in params:
dep_params[dp] = params[dp]
del params[dp]
TECseq_cmds.append(ncmd)

if len(dep_params):
TECseq.v.append(mkCommand(pcid, param_drctv, dep_params))

for cmd in TECseq_cmds:
TECseq.v.append(cmd)

if len(params):
TECseq.append(mkCommand(pcid, param_drctv, params))
if len(TECseq.items):
TECseq.v.append(mkCommand(pcid, param_drctv, params))
if hasattr(TECseq, "v") and len(TECseq.v.items):
TryEachCmd.append(TECseq)
if len(TryEachCmd.items):
seq.append(mkCommand(cid, 'directive-try-each', TryEachCmd))
Expand All @@ -114,14 +135,15 @@ def compile_manifest(options, m):
m = copy.deepcopy(m)
m['components'] += options.components
# Compile list of All Component IDs
ids = set([
# There is no ordered set, so use ordered dict instead
ids = OrderedDict.fromkeys([
SUITComponentId().from_json(id) for comp_ids in [
[c[f] for f in [
'install-id', 'download-id', 'load-id'
] if f in c] for c in m['components']
] for id in comp_ids
])
cid_data = {}
cid_data = OrderedDict()
for c in m['components']:
if not 'install-id' in c:
LOG.critical('install-id required for all components')
Expand Down Expand Up @@ -153,20 +175,19 @@ def compile_manifest(options, m):

# Construct common sequence
CommonCmds = {
'offset': lambda cid, data: mkCommand(cid, 'condition-component-offset', data['offset'])
'offset': lambda cid, data: mkCommand(cid, 'condition-component-offset', None),
'vendor-id': lambda cid, data: mkCommand(cid, 'condition-vendor-identifier', None),
'class-id': lambda cid, data: mkCommand(cid, 'condition-class-identifier', None),
}
CommonParams = {
'install-digest': lambda cid, data: ('image-digest', data['install-digest']),
'install-size': lambda cid, data: ('image-size', data['install-size']),
'vendor-id' : lambda cid, data: ('vendor-id', data['vendor-id']),
'class-id' : lambda cid, data: ('class-id', data['class-id']),
'offset' : lambda cid, data: ('offset', data['offset'])
}
CommonSeq = SUITSequence()
for cid, choices in cid_data.items():
if any(['vendor-id' in c for c in choices]):
CommonSeq.append(mkCommand(cid, 'condition-vendor-identifier',
[c['vendor-id'] for c in choices if 'vendor-id' in c][0]))
if any(['vendor-id' in c for c in choices]):
CommonSeq.append(mkCommand(cid, 'condition-class-identifier',
[c['class-id'] for c in choices if 'class-id' in c][0]))
CommonSeq = make_sequence(cid, choices, CommonSeq, CommonParams,
CommonCmds, param_drctv='directive-override-parameters')

Expand All @@ -176,12 +197,13 @@ def compile_manifest(options, m):
if any([c.get('install-on-download', True) and 'uri' in c for c in choices]):
InstParams = {
'uri' : lambda cid, data: ('uri', data['uri']),
'offset' : lambda cid, data: ('offset', data['offset']),
}
if any(['compression-info' in c and not c.get('decompress-on-load', False) for c in choices]):
InstParams['compression-info'] = lambda cid, data: data.get('compression-info')
InstCmds = {
'offset': lambda cid, data: mkCommand(
cid, 'condition-component-offset', data['offset'])
cid, 'condition-component-offset', None)
}
InstSeq = make_sequence(cid, choices, InstSeq, InstParams, InstCmds)
InstSeq.append(mkCommand(cid, 'directive-fetch', None))
Expand All @@ -191,7 +213,8 @@ def compile_manifest(options, m):
FetchParams = {
'uri' : lambda cid, data: ('uri', data['uri']),
'download-digest' : lambda cid, data : (
'image-digest', data.get('download-digest', data['install-digest']))
'image-digest', data.get('download-digest', data['install-digest'])),
'offset' : lambda cid, data: ('offset', data['offset']),
}
if any(['compression-info' in c and not c.get('decompress-on-load', False) for c in choices]):
FetchParams['compression-info'] = lambda cid, data: data.get('compression-info')
Expand Down Expand Up @@ -224,27 +247,34 @@ def compile_manifest(options, m):
LoadSeq = SUITSequence()
# If any component is marked bootable
for cid, choices in cid_data.items():
if any([c.get('bootable', False) for c in choices]):
# TODO: Dependencies
# If there are dependencies
# Verify dependencies
# Process dependencies
ValidateSeq.append(mkCommand(cid, 'condition-image-match', None))
ValidateCmds = {
# 'install-digest' : lambda cid, data : mkCommand(cid, 'condition-image-match', None)
}
ValidateParams = {
}
ValidateSeq = make_sequence(cid, choices, ValidateSeq, ValidateParams, ValidateCmds)
ValidateSeq.append(mkCommand(cid, 'condition-image-match', None))
# if any([c.get('bootable', False) for c in choices]):
# TODO: Dependencies
# If there are dependencies
# Verify dependencies
# Process dependencies


if any(['loadable' in c for c in choices]):
# Generate image load section
LoadParams = {
'install-id' : lambda cid, data : ('source-component', c['install-id']),
'load-digest' : ('image-digest', c.get('load-digest', c['install-digest'])),
'load-size' : ('image-size', c.get('load-size', c['install-size']))
'install-id' : lambda cid, data : ('source-component', c['install-id']),
'load-digest' : lambda cid, data : ('image-digest', c.get('load-digest', c['install-digest'])),
'load-size' : lambda cid, data : ('image-size', c.get('load-size', c['install-size'])),
}
if 'compression-info' in c and c.get('decompress-on-load', False):
LoadParams['compression-info'] = lambda cid, data: ('compression-info', c['compression-info'])
LoadCmds = {
# Move each loadable component
}
load_id = SUITComponentId().from_json(choices[0]['load-id'])
LoadSeq = make_sequence(load_id, choices, ValidateSeq, LoadParams, LoadCmds)
LoadSeq = make_sequence(load_id, choices, LoadSeq, LoadParams, LoadCmds)
LoadSeq.append(mkCommand(load_id, 'directive-copy', None))
LoadSeq.append(mkCommand(load_id, 'condition-image-match', None))

Expand All @@ -258,7 +288,7 @@ def compile_manifest(options, m):
'command-arg' : None
}))
else:
t = []
te = []
for c in bootable_components:
pass
# TODO: conditions
Expand All @@ -267,7 +297,7 @@ def compile_manifest(options, m):
# )
#TODO: Text
common = SUITCommon().from_json({
'components': [id.to_json() for id in ids],
'components': [id.to_json() for id in ids.keys()],
'common-sequence': CommonSeq.to_json(),
})

Expand Down
Loading

0 comments on commit ee318ee

Please sign in to comment.