Merge pull request #13486 from bergzand/pr/suit/ietf_v3
SUIT: Update to draft-ietf-v3
This commit is contained in:
commit
7d0c475113
20
Makefile.dep
20
Makefile.dep
@ -953,25 +953,29 @@ ifneq (,$(filter sock_dtls, $(USEMODULE)))
|
|||||||
USEMODULE += sock_udp
|
USEMODULE += sock_udp
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter suit_v4_%,$(USEMODULE)))
|
ifneq (,$(filter suit,$(USEMODULE)))
|
||||||
USEMODULE += suit_v4
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifneq (,$(filter suit_v4,$(USEMODULE)))
|
|
||||||
USEPKG += nanocbor
|
USEPKG += nanocbor
|
||||||
USEPKG += libcose
|
USEPKG += libcose
|
||||||
USEMODULE += libcose_crypt_c25519
|
USEMODULE += libcose_crypt_c25519
|
||||||
USEMODULE += suit_conditions
|
USEMODULE += uuid
|
||||||
|
|
||||||
|
# tests/suit_manifest has some mock implementations,
|
||||||
|
# only add the non-mock dependencies if not building that test.
|
||||||
|
ifeq (,$(filter suit_transport_mock,$(USEMODULE)))
|
||||||
# SUIT depends on riotboot support and some extra riotboot modules
|
# SUIT depends on riotboot support and some extra riotboot modules
|
||||||
FEATURES_REQUIRED += riotboot
|
FEATURES_REQUIRED += riotboot
|
||||||
USEMODULE += riotboot_slot
|
USEMODULE += riotboot_slot
|
||||||
USEMODULE += riotboot_flashwrite
|
USEMODULE += riotboot_flashwrite
|
||||||
USEMODULE += riotboot_flashwrite_verify_sha256
|
USEMODULE += riotboot_flashwrite_verify_sha256
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter suit_conditions,$(USEMODULE)))
|
ifneq (,$(filter suit_transport_%, $(USEMODULE)))
|
||||||
USEMODULE += uuid
|
USEMODULE += suit_transport
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(filter suit_transport_coap, $(USEMODULE)))
|
||||||
|
USEMODULE += nanocoap
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter suit_%,$(USEMODULE)))
|
ifneq (,$(filter suit_%,$(USEMODULE)))
|
||||||
|
|||||||
@ -480,6 +480,9 @@ ELFFILE ?= $(BINDIR)/$(APPLICATION).elf
|
|||||||
HEXFILE ?= $(ELFFILE:.elf=.hex)
|
HEXFILE ?= $(ELFFILE:.elf=.hex)
|
||||||
BINFILE ?= $(ELFFILE:.elf=.bin)
|
BINFILE ?= $(ELFFILE:.elf=.bin)
|
||||||
|
|
||||||
|
# include basic suit-tool and key handling
|
||||||
|
include $(RIOTMAKE)/suit.base.inc.mk
|
||||||
|
|
||||||
# include bootloaders support. It should be included early to allow using
|
# include bootloaders support. It should be included early to allow using
|
||||||
# variables defined in `riotboot.mk` for `FLASHFILE` before it is evaluated.
|
# variables defined in `riotboot.mk` for `FLASHFILE` before it is evaluated.
|
||||||
# It should be included after defining 'BINFILE' for 'riotboot.bin' handling.
|
# It should be included after defining 'BINFILE' for 'riotboot.bin' handling.
|
||||||
|
|||||||
2
dist/tools/flake8/check.sh
vendored
2
dist/tools/flake8/check.sh
vendored
@ -28,7 +28,7 @@ EXCLUDE="^(.+/vendor/\
|
|||||||
|dist/tools/mcuboot\
|
|dist/tools/mcuboot\
|
||||||
|dist/tools/uhcpd\
|
|dist/tools/uhcpd\
|
||||||
|dist/tools/stm32loader\
|
|dist/tools/stm32loader\
|
||||||
|dist/tools/suit_v4/suit_manifest_encoder_04)\
|
|dist/tools/suit_v3/suit-manifest-generator)\
|
||||||
|dist/tools/esptool"
|
|dist/tools/esptool"
|
||||||
FILEREGEX='(\.py$|pyterm$)'
|
FILEREGEX='(\.py$|pyterm$)'
|
||||||
FILES=$(FILEREGEX=${FILEREGEX} EXCLUDE=${EXCLUDE} changed_files)
|
FILES=$(FILEREGEX=${FILEREGEX} EXCLUDE=${EXCLUDE} changed_files)
|
||||||
|
|||||||
38
dist/tools/suit_v3/gen_key.py
vendored
Executable file
38
dist/tools/suit_v3/gen_key.py
vendored
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
# 2020 Inria
|
||||||
|
# 2020 Freie Universität 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
|
||||||
|
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||||
|
from cryptography.hazmat.primitives.serialization import Encoding
|
||||||
|
from cryptography.hazmat.primitives.serialization import PrivateFormat
|
||||||
|
from cryptography.hazmat.primitives.serialization import NoEncryption
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("usage: gen_key.py <secret filename>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
pk = Ed25519PrivateKey.generate()
|
||||||
|
pem = pk.private_bytes(encoding=Encoding.PEM,
|
||||||
|
format=PrivateFormat.PKCS8,
|
||||||
|
encryption_algorithm=NoEncryption()
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(sys.argv[1], "wb") as f:
|
||||||
|
f.write(pem)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
89
dist/tools/suit_v3/gen_manifest.py
vendored
Executable file
89
dist/tools/suit_v3/gen_manifest.py
vendored
Executable file
@ -0,0 +1,89 @@
|
|||||||
|
#!/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 argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def str2int(x):
|
||||||
|
if x.startswith("0x"):
|
||||||
|
return int(x, 16)
|
||||||
|
else:
|
||||||
|
return int(x)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
parser.add_argument('--urlroot', '-u', help='',
|
||||||
|
default="coap://example.org")
|
||||||
|
parser.add_argument('--seqnr', '-s', default=0,
|
||||||
|
help='Sequence number of the manifest')
|
||||||
|
parser.add_argument('--output', '-o', default="out.json",
|
||||||
|
help='Manifest output binary file path')
|
||||||
|
parser.add_argument('--uuid-vendor', '-V', default="riot-os.org",
|
||||||
|
help='Manifest vendor uuid')
|
||||||
|
parser.add_argument('--uuid-class', '-C', default="native",
|
||||||
|
help='Manifest class uuid')
|
||||||
|
parser.add_argument('slotfiles', nargs="+",
|
||||||
|
help='The list of slot file paths')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
uuid_vendor = uuid.uuid5(uuid.NAMESPACE_DNS, args.uuid_vendor)
|
||||||
|
uuid_class = uuid.uuid5(uuid_vendor, args.uuid_class)
|
||||||
|
|
||||||
|
template = {}
|
||||||
|
|
||||||
|
template["manifest-version"] = int(1)
|
||||||
|
template["manifest-sequence-number"] = int(args.seqnr)
|
||||||
|
|
||||||
|
images = []
|
||||||
|
for filename_offset in args.slotfiles:
|
||||||
|
split = filename_offset.split(":")
|
||||||
|
if len(split) == 1:
|
||||||
|
filename, offset = split[0], 0
|
||||||
|
else:
|
||||||
|
filename, offset = split[0], str2int(split[1])
|
||||||
|
|
||||||
|
images.append((filename, offset))
|
||||||
|
|
||||||
|
template["components"] = []
|
||||||
|
|
||||||
|
for slot, image in enumerate(images):
|
||||||
|
filename, offset = image
|
||||||
|
|
||||||
|
uri = os.path.join(args.urlroot, os.path.basename(filename))
|
||||||
|
|
||||||
|
component = {
|
||||||
|
"install-id": ["00"],
|
||||||
|
"vendor-id": uuid_vendor.hex,
|
||||||
|
"class-id": uuid_class.hex,
|
||||||
|
"file": filename,
|
||||||
|
"uri": uri,
|
||||||
|
"bootable": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset:
|
||||||
|
component.update({"offset": offset})
|
||||||
|
|
||||||
|
template["components"].append(component)
|
||||||
|
|
||||||
|
with open(args.output, 'w') as f:
|
||||||
|
json.dump(template, f, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
_args = parse_arguments()
|
||||||
|
main(_args)
|
||||||
5
dist/tools/suit_v3/suit-manifest-generator/.gitignore
vendored
Normal file
5
dist/tools/suit_v3/suit-manifest-generator/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.DS_Store
|
||||||
|
*.hex
|
||||||
|
examples/*.cbor
|
||||||
165
dist/tools/suit_v3/suit-manifest-generator/LICENSE
vendored
Normal file
165
dist/tools/suit_v3/suit-manifest-generator/LICENSE
vendored
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and
|
||||||
|
distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||||
|
owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||||
|
that control, are controlled by, or are under common control with that entity.
|
||||||
|
For the purposes of this definition, "control" means (i) the power, direct or
|
||||||
|
indirect, to cause the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||||
|
permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including
|
||||||
|
but not limited to software source code, documentation source, and configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or
|
||||||
|
translation of a Source form, including but not limited to compiled object code,
|
||||||
|
generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||||
|
available under the License, as indicated by a copyright notice that is included
|
||||||
|
in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||||
|
is based on (or derived from) the Work and for which the editorial revisions,
|
||||||
|
annotations, elaborations, or other modifications represent, as a whole, an
|
||||||
|
original work of authorship. For the purposes of this License, Derivative Works
|
||||||
|
shall not include works that remain separable from, or merely link (or bind by
|
||||||
|
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version
|
||||||
|
of the Work and any modifications or additions to that Work or Derivative Works
|
||||||
|
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||||
|
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||||
|
on behalf of the copyright owner. For the purposes of this definition,
|
||||||
|
"submitted" means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems, and
|
||||||
|
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||||
|
the purpose of discussing and improving the Work, but excluding communication
|
||||||
|
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||||
|
owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||||
|
of whom a Contribution has been received by Licensor and subsequently
|
||||||
|
incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||||
|
Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable (except as stated in this section) patent license to make, have
|
||||||
|
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||||
|
such license applies only to those patent claims licensable by such Contributor
|
||||||
|
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||||
|
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||||
|
submitted. If You institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||||
|
Contribution incorporated within the Work constitutes direct or contributory
|
||||||
|
patent infringement, then any patent licenses granted to You under this License
|
||||||
|
for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution.
|
||||||
|
|
||||||
|
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||||
|
in any medium, with or without modifications, and in Source or Object form,
|
||||||
|
provided that You meet the following conditions:
|
||||||
|
|
||||||
|
You must give any other recipients of the Work or Derivative Works a copy of
|
||||||
|
this License; and
|
||||||
|
You must cause any modified files to carry prominent notices stating that You
|
||||||
|
changed the files; and
|
||||||
|
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||||
|
all copyright, patent, trademark, and attribution notices from the Source form
|
||||||
|
of the Work, excluding those notices that do not pertain to any part of the
|
||||||
|
Derivative Works; and
|
||||||
|
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||||
|
Derivative Works that You distribute must include a readable copy of the
|
||||||
|
attribution notices contained within such NOTICE file, excluding those notices
|
||||||
|
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||||
|
following places: within a NOTICE text file distributed as part of the
|
||||||
|
Derivative Works; within the Source form or documentation, if provided along
|
||||||
|
with the Derivative Works; or, within a display generated by the Derivative
|
||||||
|
Works, if and wherever such third-party notices normally appear. The contents of
|
||||||
|
the NOTICE file are for informational purposes only and do not modify the
|
||||||
|
License. You may add Your own attribution notices within Derivative Works that
|
||||||
|
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||||
|
provided that such additional attribution notices cannot be construed as
|
||||||
|
modifying the License.
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide
|
||||||
|
additional or different license terms and conditions for use, reproduction, or
|
||||||
|
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||||
|
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||||
|
with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions.
|
||||||
|
|
||||||
|
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||||
|
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||||
|
conditions of this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||||
|
any separate license agreement you may have executed with Licensor regarding
|
||||||
|
such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks.
|
||||||
|
|
||||||
|
This License does not grant permission to use the trade names, trademarks,
|
||||||
|
service marks, or product names of the Licensor, except as required for
|
||||||
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||||
|
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||||
|
including, without limitation, any warranties or conditions of TITLE,
|
||||||
|
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||||
|
solely responsible for determining the appropriateness of using or
|
||||||
|
redistributing the Work and assume any risks associated with Your exercise of
|
||||||
|
permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability.
|
||||||
|
|
||||||
|
In no event and under no legal theory, whether in tort (including negligence),
|
||||||
|
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||||
|
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special, incidental,
|
||||||
|
or consequential damages of any character arising as a result of this License or
|
||||||
|
out of the use or inability to use the Work (including but not limited to
|
||||||
|
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such Contributor has
|
||||||
|
been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability.
|
||||||
|
|
||||||
|
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||||
|
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||||
|
other liability obligations and/or rights consistent with this License. However,
|
||||||
|
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||||
|
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||||
|
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason of your
|
||||||
|
accepting any such warranty or additional liability.
|
||||||
209
dist/tools/suit_v3/suit-manifest-generator/README.md
vendored
Normal file
209
dist/tools/suit_v3/suit-manifest-generator/README.md
vendored
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# Manifest Generator
|
||||||
|
|
||||||
|
This repository contains a tool to generate manifests following the specification in https://tools.ietf.org/html/draft-ietf-suit-manifest-03.
|
||||||
|
|
||||||
|
# Installing
|
||||||
|
|
||||||
|
First clone this repo:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/ARMmbed/suit-manifest-generator.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, use pip to install the repo:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd suit-manifest-generator
|
||||||
|
$ python3 -m pip install --user --upgrade .
|
||||||
|
```
|
||||||
|
|
||||||
|
# Input File Description
|
||||||
|
|
||||||
|
The input file is organised into four high-level elements:
|
||||||
|
|
||||||
|
* `manifest-version` (a positive integer), the version of the manifest specification
|
||||||
|
* `manifest-sequence-number` (a positive integer), the anti-rollback counter of the manifest
|
||||||
|
* `components`, a list of components that are described by the manifest
|
||||||
|
|
||||||
|
|
||||||
|
Each component is a JSON map that may contain the following elements. Some elements are required for the target to be able to install the component.
|
||||||
|
|
||||||
|
Required elements:
|
||||||
|
|
||||||
|
* `install-id` (a Component ID), the identifier of the location to install the described component.
|
||||||
|
* `install-digest` (a SUIT Digest), the digest of the component after installation.
|
||||||
|
* `install-size` (a positive integer), the size of the component after installation.
|
||||||
|
* `vendor-id` (a RFC 4122 UUID), the UUID for the component vendor. This must match the UUID that the manifest processor expects for the specified `install-id`. The suit-tool expects at least one component to have a `vendor-id`
|
||||||
|
* `class-id` (a RFC 4122 UUID), the UUID for the component. This must match the UUID that the manifest processor expects for the specified `install-id`. The `suit-tool` expects at least one component with a `vendor-id` to also have a `class-id`
|
||||||
|
* `file` (a string), the path to a payload file. The `install-digest` and `install-size` will be calculated from this file.
|
||||||
|
|
||||||
|
Some elements are not required by the tool, but are necessary in order to accomplish one or more use-cases.
|
||||||
|
|
||||||
|
Optional elements:
|
||||||
|
|
||||||
|
* `bootable` (a boolean, default: `false`), when set to true, the `suit-tool` will generate commands to execute the component, either from `install-id` or from `load-id` (see below)
|
||||||
|
* `uri` (a text string), the location at which to find the payload. This element is required in order to generate the `payload-fetch` and `install` sections.
|
||||||
|
* `loadable` (a boolean, default: `false`), when set to true, the `suit-tool` loads this component in the `load` section.
|
||||||
|
* `compression-info` (a choice of string values), indicates how a payload is compressed. When specified, payload is decompressed before installation. The `install-size` must match the decompressed size of the payload and the install-digest must match the decompressed payload. N.B. The suit-tool does not perform compression. Supported values are:
|
||||||
|
|
||||||
|
* `gzip`
|
||||||
|
* `bzip2`
|
||||||
|
* `deflate`
|
||||||
|
* `lz4`
|
||||||
|
* `lzma`
|
||||||
|
|
||||||
|
* `download-digest` (a SUIT Digest), a digest of the component after download. Only required if `compression-info` is present and `decompress-on-load` is `false`.
|
||||||
|
* `decompress-on-load` (a boolean, default: `false`), when set to true, payload is not decompressed during installation. Instead, the payload is decompressed during loading. This element has no effect if `loadable` is `false`.
|
||||||
|
* `load-digest` (a SUIT Digest), a digest of the component after loading. Only required if `decompress-on-load` is `true`.
|
||||||
|
* `install-on-download` (boolean, default: true), If true, payload is written to `install-id` during download, otherwise, payload is written to `download-id`.
|
||||||
|
* `download-id` (a component id), the location where a downloaded payload should be stored before installation--only used when `install-on-download` is `false`.
|
||||||
|
|
||||||
|
## Component ID
|
||||||
|
|
||||||
|
The `suit-tool` expects component IDs to be a JSON list of strings. The `suit-tool` converts the strings to bytes by:
|
||||||
|
|
||||||
|
1. Attempting to convert from hex
|
||||||
|
2. Attempting to convert from base64
|
||||||
|
3. Encoding the string to UTF-8
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
* `["00"]` will encode to `814100` (`[h'00']`)
|
||||||
|
* `["0"]` will encode to `814130` (`[h'30']`)
|
||||||
|
* `["MTIzNA=="]` will encode to `814431323334` (`[h'31323334']`)
|
||||||
|
* `["example"]` will encode to `81476578616D706C65` (`[h'6578616d706c65']`)
|
||||||
|
|
||||||
|
N.B. Be careful that certain strings can appear to be hex or base64 and will be treated as such. Any characters outside the set `[0-9a-fA-F]` ensure that the string is not treated as hex. Any characters outside the set `[0-9A-Za-z+/]` or a number of characters not divisible by 4 will ensure that the string is not treated as base64.
|
||||||
|
|
||||||
|
## SUIT Digest
|
||||||
|
|
||||||
|
The format of a digest is a JSON map:
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"algorithm-id" : "sha256",
|
||||||
|
"digest-bytes" : "base64-or-hex"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `algorithm-id` must be one of:
|
||||||
|
|
||||||
|
* `sha224`
|
||||||
|
* `sha256`
|
||||||
|
* `sha384`
|
||||||
|
* `sha512`
|
||||||
|
|
||||||
|
The `digest-bytes` is a string of either hex- or base64-encoded bytes. The same decoding rules as those in Component ID are applied.
|
||||||
|
|
||||||
|
## Example Input File
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"components" : [
|
||||||
|
{
|
||||||
|
"download-id" : ["01"],
|
||||||
|
"install-id" : ["00"],
|
||||||
|
"install-digest": {
|
||||||
|
"algorithm-id": "sha256",
|
||||||
|
"digest-bytes": "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210"
|
||||||
|
},
|
||||||
|
"install-size" : 34768,
|
||||||
|
"uri": "http://example.com/file.bin",
|
||||||
|
"vendor-id" : "fa6b4a53-d5ad-5fdf-be9d-e663e4d41ffe",
|
||||||
|
"class-id" : "1492af14-2569-5e48-bf42-9b2d51f2ab45",
|
||||||
|
"bootable" : true,
|
||||||
|
"install-on-download" : false,
|
||||||
|
"loadable" : true,
|
||||||
|
"decompress-on-load" : true,
|
||||||
|
"load-id" : ["02"],
|
||||||
|
"compression-info" : "gzip",
|
||||||
|
"load-digest" : {
|
||||||
|
"algorithm-id": "sha256",
|
||||||
|
"digest-bytes": "0011223344556677889901234567899876543210aabbccddeeffabcdeffedcba"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"install-id" : ["03", "01"],
|
||||||
|
"install-digest": {
|
||||||
|
"algorithm-id": "sha256",
|
||||||
|
"digest-bytes": "0123456789abcdeffedcba987654321000112233445566778899aabbccddeeff"
|
||||||
|
},
|
||||||
|
"install-size" : 76834,
|
||||||
|
"uri": "http://example.com/file2.bin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"manifest-version": 1,
|
||||||
|
"manifest-sequence-number": 7
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Invoking the suit-tool
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The `suit-tool` has a configurable log level, specified with `-l`:
|
||||||
|
|
||||||
|
* `suit-tool -l debug` verbose output
|
||||||
|
* `suit-tool -l info` normal output
|
||||||
|
* `suit-tool -l warning` suppress informational messages
|
||||||
|
* `suit-tool -l exception` suppress warning and informational messages
|
||||||
|
|
||||||
|
## Create
|
||||||
|
|
||||||
|
To create a manifest, invoke the `suit-tool` with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
suit-tool create -i IFILE -o OFILE
|
||||||
|
```
|
||||||
|
|
||||||
|
The format of `IFILE` is as described above. `OFILE` defaults to a CBOR-encoded SUIT manifest.
|
||||||
|
|
||||||
|
`-f` specifies the output format:
|
||||||
|
|
||||||
|
* `suit`: CBOR-encoded SUIT manifest
|
||||||
|
* `suit-debug`: CBOR-debug SUIT manifest
|
||||||
|
* `json`: JSON-representation of a SUIT manifest
|
||||||
|
|
||||||
|
The `suit-tool` can generate a manifest with severable fields. To enable this mode, add the `-s` flag.
|
||||||
|
|
||||||
|
To add a component to the manifest from the command-line, use the following syntax:
|
||||||
|
|
||||||
|
```
|
||||||
|
-c 'FIELD1=VALUE1,FIELD2=VALUE2'
|
||||||
|
```
|
||||||
|
|
||||||
|
The supported fields are:
|
||||||
|
|
||||||
|
* `file` the path to a file to use as a payload file.
|
||||||
|
* `inst` the `install-id`.
|
||||||
|
* `uri` the URI where the file will be found.
|
||||||
|
|
||||||
|
## Sign
|
||||||
|
|
||||||
|
To sign an existing manifest, invoke the `suit-tool` with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
suit-tool sign -m MANIFEST -k PRIVKEY -o OFILE
|
||||||
|
```
|
||||||
|
|
||||||
|
`PRIVKEY` must be a secp256r1 ECC private key in PEM format.
|
||||||
|
|
||||||
|
If the COSE Signature needs to indicate the key ID, add a key id with:
|
||||||
|
|
||||||
|
```
|
||||||
|
-i KEYID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parse
|
||||||
|
|
||||||
|
To parse an existing manifest, invoke the `suit-tool` with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
suit-tool parse -m MANIFEST
|
||||||
|
```
|
||||||
|
|
||||||
|
If a json-representation is needed, add the '-j' flag.
|
||||||
31
dist/tools/suit_v3/suit-manifest-generator/bin/suit-tool
vendored
Executable file
31
dist/tools/suit_v3/suit-manifest-generator/bin/suit-tool
vendored
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copyright 2016-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 sys
|
||||||
|
import os
|
||||||
|
suittoolPath = os.path.join(os.path.dirname(os.path.realpath(__file__)),'..')
|
||||||
|
sys.path.insert(0,suittoolPath)
|
||||||
|
from suit_tool import clidriver
|
||||||
|
|
||||||
|
def main():
|
||||||
|
return clidriver.main()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
67
dist/tools/suit_v3/suit-manifest-generator/setup.py
vendored
Normal file
67
dist/tools/suit_v3/suit-manifest-generator/setup.py
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copyright 2020 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 setuptools
|
||||||
|
import os
|
||||||
|
import suit_tool
|
||||||
|
|
||||||
|
with open('README.md', 'r') as fd:
|
||||||
|
long_description = fd.read()
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
entry_points={
|
||||||
|
"console_scripts": [
|
||||||
|
"suit-tool=suit_tool.clidriver:main",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
scripts = []
|
||||||
|
else:
|
||||||
|
platform_deps = []
|
||||||
|
# entry points are nice, but add ~100ms to startup time with all the
|
||||||
|
# pkg_resources infrastructure, so we use scripts= instead on unix-y
|
||||||
|
# platforms:
|
||||||
|
scripts = ['bin/suit-tool', ]
|
||||||
|
entry_points = {}
|
||||||
|
|
||||||
|
setuptools.setup (
|
||||||
|
name = 'ietf-suit-tool',
|
||||||
|
version = suit_tool.__version__,
|
||||||
|
author = 'Brendan Moran',
|
||||||
|
author_email = 'brendan.moran@arm.com',
|
||||||
|
description = 'A tool for constructing SUIT manifests',
|
||||||
|
long_description = long_description,
|
||||||
|
url = 'https://github.com/ARMmbed/suit-manifest-generator',
|
||||||
|
packages = setuptools.find_packages(exclude=['examples*', 'parser_examples*', '.git*']),
|
||||||
|
python_requires ='>=3.6',
|
||||||
|
scripts = scripts,
|
||||||
|
entry_points = entry_points,
|
||||||
|
zip_safe = False,
|
||||||
|
install_requires = [
|
||||||
|
'cbor>=1.0.0',
|
||||||
|
'colorama>=0.4.0',
|
||||||
|
'cryptography>=2.8'
|
||||||
|
],
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: Apache Software License",
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Operating System :: OS Independent"
|
||||||
|
],
|
||||||
|
long_description_content_type = 'text/markdown'
|
||||||
|
)
|
||||||
20
dist/tools/suit_v3/suit-manifest-generator/suit_tool/__init__.py
vendored
Normal file
20
dist/tools/suit_v3/suit-manifest-generator/suit_tool/__init__.py
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copyright 2016-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.
|
||||||
|
#
|
||||||
|
__version__ = '0.0.1'
|
||||||
85
dist/tools/suit_v3/suit-manifest-generator/suit_tool/argparser.py
vendored
Normal file
85
dist/tools/suit_v3/suit-manifest-generator/suit_tool/argparser.py
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copyright 2019-2020 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 sys
|
||||||
|
import argparse
|
||||||
|
from suit_tool import __version__
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_component(s):
|
||||||
|
types = {
|
||||||
|
'file' : ('file', 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):
|
||||||
|
self.parser = self._make_parser()
|
||||||
|
|
||||||
|
def _make_parser(self):
|
||||||
|
parser = argparse.ArgumentParser(description = 'Create or transform a manifest.'
|
||||||
|
' Use {} [command] -h for help on each command.'.format(sys.argv[0]))
|
||||||
|
|
||||||
|
# Add all top-level commands
|
||||||
|
parser.add_argument('-l', '--log-level', choices=['debug','info','warning','exception'], default='info',
|
||||||
|
help='Set the verbosity level of console output.')
|
||||||
|
parser.add_argument('--version', action='version', version=__version__,
|
||||||
|
help='display the version'
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="action")
|
||||||
|
subparsers.required = True
|
||||||
|
create_parser = subparsers.add_parser('create', help='Create a new manifest')
|
||||||
|
|
||||||
|
# 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.')
|
||||||
|
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.')
|
||||||
|
create_parser.add_argument('-c', '--add-component', action='append', type=str_to_component, dest='components', default=[])
|
||||||
|
|
||||||
|
sign_parser = subparsers.add_parser('sign', help='Sign a manifest')
|
||||||
|
|
||||||
|
sign_parser.add_argument('-m', '--manifest', metavar='FILE', type=argparse.FileType('rb'), required=True)
|
||||||
|
sign_parser.add_argument('-k', '--private-key', metavar='FILE', type=argparse.FileType('rb'), required=True)
|
||||||
|
sign_parser.add_argument('-i', '--key-id', metavar='ID', type=str)
|
||||||
|
sign_parser.add_argument('-o', '--output-file', metavar='FILE', type=argparse.FileType('wb'), required=True)
|
||||||
|
|
||||||
|
parse_parser = subparsers.add_parser('parse', help='Parse a manifest')
|
||||||
|
|
||||||
|
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_uecc_pubkey_parser.add_argument('-k', '--private-key', metavar='FILE', type=argparse.FileType('rb'), required=True)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(self, args=None):
|
||||||
|
self.options = self.parser.parse_args(args)
|
||||||
|
|
||||||
|
return self
|
||||||
63
dist/tools/suit_v3/suit-manifest-generator/suit_tool/clidriver.py
vendored
Normal file
63
dist/tools/suit_v3/suit-manifest-generator/suit_tool/clidriver.py
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copyright 2018-2020 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 logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from suit_tool.argparser import MainArgumentParser
|
||||||
|
from suit_tool import create, sign, parse, get_uecc_pubkey
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.options = MainArgumentParser().parse_args().options
|
||||||
|
|
||||||
|
log_level = {
|
||||||
|
'debug': logging.DEBUG,
|
||||||
|
'info': logging.INFO,
|
||||||
|
'warning': logging.WARNING,
|
||||||
|
'exception': logging.CRITICAL
|
||||||
|
}[self.options.log_level]
|
||||||
|
logging.basicConfig(level=log_level,
|
||||||
|
format=LOG_FORMAT,
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
LOG.debug('CLIDriver created. Arguments parsed and logging setup.')
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
rc = {
|
||||||
|
"create": create.main,
|
||||||
|
"parse": parse.main,
|
||||||
|
# "verify": verify.main,
|
||||||
|
# "cert": cert.main,
|
||||||
|
# "init": init.main,
|
||||||
|
# "update" : update.main,
|
||||||
|
"pubkey": get_uecc_pubkey.main,
|
||||||
|
"sign": sign.main
|
||||||
|
}[self.options.action](self.options) or 0
|
||||||
|
|
||||||
|
sys.exit(rc)
|
||||||
289
dist/tools/suit_v3/suit-manifest-generator/suit_tool/compile.py
vendored
Normal file
289
dist/tools/suit_v3/suit-manifest-generator/suit_tool/compile.py
vendored
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
#!/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 binascii
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
|
||||||
|
|
||||||
|
from suit_tool.manifest import SUITComponentId, SUITCommon, SUITSequence, \
|
||||||
|
SUITCommand, \
|
||||||
|
SUITWrapper, SUITTryEach
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def runable_id(c):
|
||||||
|
id = c['install-id']
|
||||||
|
if c.get('loadable'):
|
||||||
|
id = c['load-id']
|
||||||
|
return id
|
||||||
|
|
||||||
|
def hash_file(fname, alg):
|
||||||
|
imgsize = 0
|
||||||
|
digest = hashes.Hash(alg, backend=default_backend())
|
||||||
|
with open(fname, 'rb') as fd:
|
||||||
|
def read_in_chunks():
|
||||||
|
while True:
|
||||||
|
data = fd.read(1024)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
yield data
|
||||||
|
for chunk in read_in_chunks():
|
||||||
|
imgsize += len(chunk)
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest, imgsize
|
||||||
|
|
||||||
|
|
||||||
|
def mkCommand(cid, name, arg):
|
||||||
|
if hasattr(arg, 'to_json'):
|
||||||
|
jarg = arg.to_json()
|
||||||
|
else:
|
||||||
|
jarg = arg
|
||||||
|
return SUITCommand().from_json({
|
||||||
|
'component-id' : cid.to_json(),
|
||||||
|
'command-id' : name,
|
||||||
|
'command-arg' : jarg
|
||||||
|
})
|
||||||
|
|
||||||
|
def check_eq(ids, choices):
|
||||||
|
eq = {}
|
||||||
|
neq = {}
|
||||||
|
|
||||||
|
check = lambda x: x[:-1]==x[1:]
|
||||||
|
get = lambda k, l: [d.get(k) for d in l]
|
||||||
|
eq = { k: ids[k] for k in ids if any([k in c for c in choices]) and check(get(k, choices)) }
|
||||||
|
check = lambda x: not x[:-1]==x[1:]
|
||||||
|
neq = { k: ids[k] for k in ids if any([k in c for c in choices]) and check(get(k, choices)) }
|
||||||
|
return eq, neq
|
||||||
|
|
||||||
|
|
||||||
|
def make_sequence(cid, choices, seq, params, cmds, pcid_key=None, param_drctv='directive-set-parameters'):
|
||||||
|
eqcmds, neqcmds = check_eq(cmds, choices)
|
||||||
|
eqparams, neqparams = check_eq(params, choices)
|
||||||
|
if not pcid_key:
|
||||||
|
pcid = cid
|
||||||
|
else:
|
||||||
|
pcid = SUITComponentId().from_json(choices[0][pcid_key])
|
||||||
|
params = {}
|
||||||
|
for param, pcmd in eqparams.items():
|
||||||
|
k,v = pcmd(pcid, choices[0])
|
||||||
|
params[k] = v
|
||||||
|
if len(params):
|
||||||
|
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))
|
||||||
|
params = {}
|
||||||
|
for param, pcmd in neqparams.items():
|
||||||
|
k,v = pcmd(cid, c)
|
||||||
|
params[k] = v
|
||||||
|
if len(params):
|
||||||
|
TECseq.append(mkCommand(pcid, param_drctv, params))
|
||||||
|
if len(TECseq.items):
|
||||||
|
TryEachCmd.append(TECseq)
|
||||||
|
if len(TryEachCmd.items):
|
||||||
|
seq.append(mkCommand(cid, 'directive-try-each', TryEachCmd))
|
||||||
|
# Finally, and equal commands
|
||||||
|
for item, cmd in eqcmds.items():
|
||||||
|
seq.append(cmd(cid, choices[0]))
|
||||||
|
return seq
|
||||||
|
|
||||||
|
def compile_manifest(options, m):
|
||||||
|
m = copy.deepcopy(m)
|
||||||
|
m['components'] += options.components
|
||||||
|
# Compile list of All Component IDs
|
||||||
|
ids = set([
|
||||||
|
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 = {}
|
||||||
|
for c in m['components']:
|
||||||
|
if not 'install-id' in c:
|
||||||
|
LOG.critical('install-id required for all components')
|
||||||
|
raise Exception('No install-id')
|
||||||
|
|
||||||
|
cid = SUITComponentId().from_json(c['install-id'])
|
||||||
|
if not cid in cid_data:
|
||||||
|
cid_data[cid] = [c]
|
||||||
|
else:
|
||||||
|
cid_data[cid].append(c)
|
||||||
|
|
||||||
|
for id, choices in cid_data.items():
|
||||||
|
for c in choices:
|
||||||
|
if 'file' in c:
|
||||||
|
digest, imgsize = hash_file(c['file'], hashes.SHA256())
|
||||||
|
c['install-digest'] = {
|
||||||
|
'algorithm-id' : 'sha256',
|
||||||
|
'digest-bytes' : binascii.b2a_hex(digest.finalize())
|
||||||
|
}
|
||||||
|
c['install-size'] = imgsize
|
||||||
|
|
||||||
|
if not any(c.get('vendor-id', None) for c in m['components']):
|
||||||
|
LOG.critical('A vendor-id is required for at least one component')
|
||||||
|
raise Exception('No Vendor ID')
|
||||||
|
|
||||||
|
if not any(c.get('class-id', None) for c in m['components'] if 'vendor-id' in c):
|
||||||
|
LOG.critical('A class-id is required for at least one component that also has a vendor-id')
|
||||||
|
raise Exception('No Class ID')
|
||||||
|
|
||||||
|
# Construct common sequence
|
||||||
|
CommonCmds = {
|
||||||
|
'offset': lambda cid, data: mkCommand(cid, 'condition-component-offset', data['offset'])
|
||||||
|
}
|
||||||
|
CommonParams = {
|
||||||
|
'install-digest': lambda cid, data: ('image-digest', data['install-digest']),
|
||||||
|
'install-size': lambda cid, data: ('image-size', data['install-size']),
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
|
||||||
|
InstSeq = SUITSequence()
|
||||||
|
FetchSeq = SUITSequence()
|
||||||
|
for cid, choices in cid_data.items():
|
||||||
|
if any([c.get('install-on-download', True) and 'uri' in c for c in choices]):
|
||||||
|
InstParams = {
|
||||||
|
'uri' : lambda cid, data: ('uri', data['uri']),
|
||||||
|
}
|
||||||
|
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'])
|
||||||
|
}
|
||||||
|
InstSeq = make_sequence(cid, choices, InstSeq, InstParams, InstCmds)
|
||||||
|
InstSeq.append(mkCommand(cid, 'directive-fetch', None))
|
||||||
|
InstSeq.append(mkCommand(cid, 'condition-image-match', None))
|
||||||
|
|
||||||
|
elif any(['uri' in c for c in choices]):
|
||||||
|
FetchParams = {
|
||||||
|
'uri' : lambda cid, data: ('uri', data['uri']),
|
||||||
|
'download-digest' : lambda cid, data : (
|
||||||
|
'image-digest', data.get('download-digest', data['install-digest']))
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
|
||||||
|
FetchCmds = {
|
||||||
|
'offset': lambda cid, data: mkCommand(
|
||||||
|
cid, 'condition-component-offset', data['offset']),
|
||||||
|
'fetch' : lambda cid, data: mkCommand(
|
||||||
|
data.get('download-id', cid.to_json()), 'directive-fetch', None),
|
||||||
|
'match' : lambda cid, data: mkCommand(
|
||||||
|
data.get('download-id', cid.to_json()), 'condition-image-match', None)
|
||||||
|
}
|
||||||
|
FetchSeq = make_sequence(cid, choices, FetchSeq, FetchParams, FetchCmds, 'download-id')
|
||||||
|
|
||||||
|
InstParams = {
|
||||||
|
'download-id' : lambda cid, data : ('source-component', data['download-id'])
|
||||||
|
}
|
||||||
|
InstCmds = {
|
||||||
|
}
|
||||||
|
InstSeq = make_sequence(cid, choices, InstSeq, InstParams, InstCmds)
|
||||||
|
InstSeq.append(mkCommand(cid, 'directive-copy', None))
|
||||||
|
InstSeq.append(mkCommand(cid, 'condition-image-match', None))
|
||||||
|
|
||||||
|
# TODO: Dependencies
|
||||||
|
# If there are dependencies
|
||||||
|
# Construct dependency resolution step
|
||||||
|
|
||||||
|
ValidateSeq = SUITSequence()
|
||||||
|
RunSeq = SUITSequence()
|
||||||
|
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))
|
||||||
|
|
||||||
|
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']))
|
||||||
|
}
|
||||||
|
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.append(mkCommand(load_id, 'directive-copy', None))
|
||||||
|
LoadSeq.append(mkCommand(load_id, 'condition-image-match', None))
|
||||||
|
|
||||||
|
# Generate image invocation section
|
||||||
|
bootable_components = [x for x in m['components'] if x.get('bootable')]
|
||||||
|
if len(bootable_components) == 1:
|
||||||
|
c = bootable_components[0]
|
||||||
|
RunSeq.append(SUITCommand().from_json({
|
||||||
|
'component-id' : runable_id(c),
|
||||||
|
'command-id' : 'directive-run',
|
||||||
|
'command-arg' : None
|
||||||
|
}))
|
||||||
|
else:
|
||||||
|
t = []
|
||||||
|
for c in bootable_components:
|
||||||
|
pass
|
||||||
|
# TODO: conditions
|
||||||
|
# t.append(
|
||||||
|
#
|
||||||
|
# )
|
||||||
|
#TODO: Text
|
||||||
|
common = SUITCommon().from_json({
|
||||||
|
'components': [id.to_json() for id in ids],
|
||||||
|
'common-sequence': CommonSeq.to_json(),
|
||||||
|
})
|
||||||
|
|
||||||
|
jmanifest = {
|
||||||
|
'manifest-version' : m['manifest-version'],
|
||||||
|
'manifest-sequence-number' : m['manifest-sequence-number'],
|
||||||
|
'common' : common.to_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
jmanifest.update({k:v for k,v in {
|
||||||
|
'payload-fetch' : FetchSeq.to_json(),
|
||||||
|
'install' : InstSeq.to_json(),
|
||||||
|
'validate' : ValidateSeq.to_json(),
|
||||||
|
'run' : RunSeq.to_json(),
|
||||||
|
'load' : LoadSeq.to_json()
|
||||||
|
}.items() if v})
|
||||||
|
|
||||||
|
wrapped_manifest = SUITWrapper().from_json({'manifest' : jmanifest})
|
||||||
|
return wrapped_manifest
|
||||||
41
dist/tools/suit_v3/suit-manifest-generator/suit_tool/create.py
vendored
Normal file
41
dist/tools/suit_v3/suit-manifest-generator/suit_tool/create.py
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#!/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.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
from suit_tool.compile import compile_manifest
|
||||||
|
import json
|
||||||
|
import cbor
|
||||||
|
import itertools
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
def main(options):
|
||||||
|
m = json.loads(options.input_file.read())
|
||||||
|
|
||||||
|
nm = compile_manifest(options, m)
|
||||||
|
if hasattr(options, 'severable') and options.severable:
|
||||||
|
nm = nm.to_severable()
|
||||||
|
output = {
|
||||||
|
'suit' : lambda x: cbor.dumps(x.to_suit(), sort_keys=True),
|
||||||
|
'suit-debug' : lambda x: '\n'.join(itertools.chain.from_iterable(
|
||||||
|
map(textwrap.wrap, x.to_debug('').split('\n'))
|
||||||
|
)).encode('utf-8'),
|
||||||
|
'json' : lambda x : json.dumps(x.to_json(), indent=2).encode('utf-8')
|
||||||
|
}.get(options.format)(nm)
|
||||||
|
options.output_file.write(output)
|
||||||
|
|
||||||
|
return 0
|
||||||
53
dist/tools/suit_v3/suit-manifest-generator/suit_tool/get_uecc_pubkey.py
vendored
Normal file
53
dist/tools/suit_v3/suit-manifest-generator/suit_tool/get_uecc_pubkey.py
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Copyright 2020 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 textwrap
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization as ks
|
||||||
|
|
||||||
|
def main(options):
|
||||||
|
private_key = ks.load_pem_private_key(
|
||||||
|
options.private_key.read(),
|
||||||
|
password=None,
|
||||||
|
backend=default_backend()
|
||||||
|
)
|
||||||
|
#public_numbers = private_key.public_key().public_numbers()
|
||||||
|
#x = public_numbers.x
|
||||||
|
#y = public_numbers.y
|
||||||
|
#uecc_bytes = x.to_bytes(
|
||||||
|
# (x.bit_length() + 7) // 8, byteorder='big'
|
||||||
|
#) + y.to_bytes(
|
||||||
|
# (y.bit_length() + 7) // 8, byteorder='big'
|
||||||
|
#)
|
||||||
|
#uecc_c_def = ['const uint8_t public_key[] = {'] + textwrap.wrap(
|
||||||
|
# ', '.join(['{:0=#4x}'.format(x) for x in uecc_bytes]),
|
||||||
|
# 76
|
||||||
|
#)
|
||||||
|
public_bytes = private_key.public_key().public_bytes(
|
||||||
|
encoding=ks.Encoding.Raw,
|
||||||
|
format=ks.PublicFormat.Raw
|
||||||
|
)
|
||||||
|
|
||||||
|
c_def = ['const uint8_t public_key[] = {'] + textwrap.wrap(
|
||||||
|
', '.join(['{:0=#4x}'.format(x) for x in public_bytes]),
|
||||||
|
76
|
||||||
|
)
|
||||||
|
print('\n '.join(c_def) + '\n};')
|
||||||
|
return 0
|
||||||
688
dist/tools/suit_v3/suit-manifest-generator/suit_tool/manifest.py
vendored
Normal file
688
dist/tools/suit_v3/suit-manifest-generator/suit_tool/manifest.py
vendored
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
#!/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
|
||||||
37
dist/tools/suit_v3/suit-manifest-generator/suit_tool/parse.py
vendored
Normal file
37
dist/tools/suit_v3/suit-manifest-generator/suit_tool/parse.py
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/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 cbor
|
||||||
|
import json
|
||||||
|
import itertools
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from suit_tool.manifest import SUITWrapper
|
||||||
|
|
||||||
|
def main(options):
|
||||||
|
# Read the manifest wrapper
|
||||||
|
decoded_cbor_wrapper = cbor.loads(options.manifest.read())
|
||||||
|
wrapper = SUITWrapper().from_suit(decoded_cbor_wrapper)
|
||||||
|
if options.json:
|
||||||
|
print (json.dumps(wrapper.to_json(),indent=2))
|
||||||
|
else:
|
||||||
|
print ('\n'.join(itertools.chain.from_iterable(
|
||||||
|
[textwrap.wrap(t, 70) for t in wrapper.to_debug('').split('\n')]
|
||||||
|
)))
|
||||||
|
return 0
|
||||||
111
dist/tools/suit_v3/suit-manifest-generator/suit_tool/sign.py
vendored
Normal file
111
dist/tools/suit_v3/suit-manifest-generator/suit_tool/sign.py
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#!/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 cbor
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import utils as asymmetric_utils
|
||||||
|
from cryptography.hazmat.primitives import serialization as ks
|
||||||
|
|
||||||
|
|
||||||
|
from suit_tool.manifest import COSE_Sign1, COSEList, \
|
||||||
|
SUITWrapper, SUITBytes, SUITBWrapField
|
||||||
|
import logging
|
||||||
|
import binascii
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def get_cose_es_bytes(private_key, sig_val):
|
||||||
|
ASN1_signature = private_key.sign(sig_val, ec.ECDSA(hashes.SHA256()))
|
||||||
|
r,s = asymmetric_utils.decode_dss_signature(ASN1_signature)
|
||||||
|
ssize = private_key.key_size
|
||||||
|
signature_bytes = r.to_bytes(ssize//8, byteorder='big') + s.to_bytes(ssize//8, byteorder='big')
|
||||||
|
return signature_bytes
|
||||||
|
|
||||||
|
def get_cose_ed25519_bytes(private_key, sig_val):
|
||||||
|
return private_key.sign(sig_val)
|
||||||
|
|
||||||
|
def main(options):
|
||||||
|
# Read the manifest wrapper
|
||||||
|
wrapper = cbor.loads(options.manifest.read())
|
||||||
|
|
||||||
|
private_key = None
|
||||||
|
digest = None
|
||||||
|
try:
|
||||||
|
private_key = ks.load_pem_private_key(options.private_key.read(), password=None, backend=default_backend())
|
||||||
|
if isinstance(private_key, ec.EllipticCurvePrivateKey):
|
||||||
|
options.key_type = 'ES{}'.format(private_key.key_size)
|
||||||
|
elif isinstance(private_key, ed25519.Ed25519PrivateKey):
|
||||||
|
options.key_type = 'EdDSA'
|
||||||
|
else:
|
||||||
|
LOG.critical('Unrecognized key: {}'.format(type(private_key).__name__))
|
||||||
|
return 1
|
||||||
|
digest = {
|
||||||
|
'ES256' : hashes.Hash(hashes.SHA256(), backend=default_backend()),
|
||||||
|
'ES384' : hashes.Hash(hashes.SHA384(), backend=default_backend()),
|
||||||
|
'ES512' : hashes.Hash(hashes.SHA512(), backend=default_backend()),
|
||||||
|
'EdDSA' : hashes.Hash(hashes.SHA256(), backend=default_backend()),
|
||||||
|
}.get(options.key_type)
|
||||||
|
except:
|
||||||
|
digest= hashes.Hash(hashes.SHA256(), backend=default_backend())
|
||||||
|
# private_key = None
|
||||||
|
# TODO: Implement loading of DSA keys not supported by python cryptography
|
||||||
|
LOG.critical('Non-library key type not implemented')
|
||||||
|
# return 1
|
||||||
|
|
||||||
|
digest.update(cbor.dumps(wrapper[SUITWrapper.fields['manifest'].suit_key]))
|
||||||
|
|
||||||
|
cose_signature = COSE_Sign1().from_json({
|
||||||
|
'protected' : {
|
||||||
|
'alg' : options.key_type
|
||||||
|
},
|
||||||
|
'unprotected' : {},
|
||||||
|
'payload' : {
|
||||||
|
'algorithm-id' : 'sha256',
|
||||||
|
'digest-bytes' : binascii.b2a_hex(digest.finalize())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Sig_structure = cbor.dumps([
|
||||||
|
"Signature1",
|
||||||
|
cose_signature.protected.to_suit(),
|
||||||
|
b'',
|
||||||
|
cose_signature.payload.to_suit(),
|
||||||
|
], sort_keys = True)
|
||||||
|
sig_val = Sig_structure
|
||||||
|
|
||||||
|
signature_bytes = {
|
||||||
|
'ES256' : get_cose_es_bytes,
|
||||||
|
'ES384' : get_cose_es_bytes,
|
||||||
|
'ES512' : get_cose_es_bytes,
|
||||||
|
'EdDSA' : get_cose_ed25519_bytes,
|
||||||
|
}.get(options.key_type)(private_key, sig_val)
|
||||||
|
|
||||||
|
cose_signature.signature = SUITBytes().from_suit(signature_bytes)
|
||||||
|
|
||||||
|
auth = SUITBWrapField(COSEList)().from_json([{
|
||||||
|
'COSE_Sign1_Tagged' : cose_signature.to_json()
|
||||||
|
}])
|
||||||
|
|
||||||
|
wrapper[SUITWrapper.fields['auth'].suit_key] = auth.to_suit()
|
||||||
|
|
||||||
|
options.output_file.write(cbor.dumps(wrapper, sort_keys=True))
|
||||||
|
return 0
|
||||||
33
dist/tools/suit_v4/gen_key.py
vendored
33
dist/tools/suit_v4/gen_key.py
vendored
@ -1,33 +0,0 @@
|
|||||||
#!/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 <secret filename> <public filename>")
|
|
||||||
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()
|
|
||||||
94
dist/tools/suit_v4/gen_manifest.py
vendored
94
dist/tools/suit_v4/gen_manifest.py
vendored
@ -1,94 +0,0 @@
|
|||||||
#!/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 argparse
|
|
||||||
|
|
||||||
from suit_manifest_encoder_04 import compile_to_suit
|
|
||||||
|
|
||||||
|
|
||||||
def str2int(x):
|
|
||||||
if x.startswith("0x"):
|
|
||||||
return int(x, 16)
|
|
||||||
else:
|
|
||||||
return int(x)
|
|
||||||
|
|
||||||
|
|
||||||
def sha256_from_file(filepath):
|
|
||||||
sha256 = hashlib.sha256()
|
|
||||||
sha256.update(open(filepath, "rb").read())
|
|
||||||
return sha256.digest()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--template', '-t', help='Manifest template file path')
|
|
||||||
parser.add_argument('--urlroot', '-u', help='')
|
|
||||||
parser.add_argument('--offsets', '-O', help='')
|
|
||||||
parser.add_argument('--seqnr', '-s',
|
|
||||||
help='Sequence number of the manifest')
|
|
||||||
parser.add_argument('--output', '-o', nargs='?',
|
|
||||||
help='Manifest output binary file path')
|
|
||||||
parser.add_argument('--uuid-vendor', '-V',
|
|
||||||
help='Manifest vendor uuid')
|
|
||||||
parser.add_argument('--uuid-class', '-C',
|
|
||||||
help='Manifest class uuid')
|
|
||||||
parser.add_argument('slotfiles', nargs=2,
|
|
||||||
help='The list of slot file paths')
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
|
||||||
uuid_vendor = uuid.uuid5(uuid.NAMESPACE_DNS, args.uuid_vendor)
|
|
||||||
uuid_class = uuid.uuid5(uuid_vendor, args.uuid_class)
|
|
||||||
with open(args.template, 'r') as f:
|
|
||||||
template = json.load(f)
|
|
||||||
|
|
||||||
template["sequence-number"] = int(args.seqnr)
|
|
||||||
template["conditions"] = [
|
|
||||||
{"condition-vendor-id": uuid_vendor.hex},
|
|
||||||
{"condition-class-id": uuid_class.hex},
|
|
||||||
]
|
|
||||||
|
|
||||||
offsets = [str2int(offset) for offset in args.offsets.split(",")]
|
|
||||||
|
|
||||||
for slot, slotfile in enumerate(args.slotfiles):
|
|
||||||
filename = slotfile
|
|
||||||
size = os.path.getsize(filename)
|
|
||||||
uri = os.path.join(args.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 args.output is not None:
|
|
||||||
with open(args.output, 'wb') as f:
|
|
||||||
f.write(result)
|
|
||||||
else:
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
_args = parse_arguments()
|
|
||||||
main(_args)
|
|
||||||
154
dist/tools/suit_v4/sign-04.py
vendored
154
dist/tools/suit_v4/sign-04.py
vendored
@ -1,154 +0,0 @@
|
|||||||
#!/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()
|
|
||||||
411
dist/tools/suit_v4/suit_manifest_encoder_04.py
vendored
411
dist/tools/suit_v4/suit_manifest_encoder_04.py
vendored
@ -1,411 +0,0 @@
|
|||||||
#!/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)
|
|
||||||
33
dist/tools/suit_v4/test-2img.json
vendored
33
dist/tools/suit_v4/test-2img.json
vendored
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"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"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -44,10 +44,7 @@ QUIET ?= 1
|
|||||||
#
|
#
|
||||||
|
|
||||||
USEMODULE += nanocoap_sock sock_util
|
USEMODULE += nanocoap_sock sock_util
|
||||||
USEMODULE += suit suit_coap
|
USEMODULE += suit suit_transport_coap
|
||||||
|
|
||||||
# SUIT draft v4 support:
|
|
||||||
USEMODULE += suit_v4
|
|
||||||
|
|
||||||
# Display a progress bar during firmware download
|
# Display a progress bar during firmware download
|
||||||
USEMODULE += progress_bar
|
USEMODULE += progress_bar
|
||||||
@ -97,6 +94,10 @@ TEST_EXTRA_FILES += $(SLOT_RIOT_ELFS) $(SUIT_SEC) $(SUIT_PUB)
|
|||||||
# a terminal is opened to synchronize.
|
# a terminal is opened to synchronize.
|
||||||
TESTRUNNER_RESET_AFTER_TERM ?= 1
|
TESTRUNNER_RESET_AFTER_TERM ?= 1
|
||||||
|
|
||||||
|
# This can be removed as soon as the Pi-fleet runners support an Openssl version
|
||||||
|
# with ed25519 support.
|
||||||
|
TEST_ON_CI_BLACKLIST = all
|
||||||
|
|
||||||
include $(RIOTBASE)/Makefile.include
|
include $(RIOTBASE)/Makefile.include
|
||||||
|
|
||||||
.PHONY: host-tools
|
.PHONY: host-tools
|
||||||
|
|||||||
@ -127,7 +127,7 @@ In another terminal, run:
|
|||||||
[setup-wireless]: #Setup-a-wireless-device-behind-a-border-router
|
[setup-wireless]: #Setup-a-wireless-device-behind-a-border-router
|
||||||
|
|
||||||
If the workflow for updating using ethos is successful, you can try doing the
|
If the workflow for updating using ethos is successful, you can try doing the
|
||||||
same over "real" network interfaces, by updating a node that is connected
|
same over wireless network interfaces, by updating a node that is connected
|
||||||
wirelessly with a border router in between.
|
wirelessly with a border router in between.
|
||||||
|
|
||||||
Depending on your device you can use BLE or 802.15.4.
|
Depending on your device you can use BLE or 802.15.4.
|
||||||
@ -369,22 +369,59 @@ see 6 pairs of messages indicating where (filepath) the file was published and
|
|||||||
the corresponding coap resource URI
|
the corresponding coap resource URI
|
||||||
|
|
||||||
...
|
...
|
||||||
published "/home/francisco/workspace/RIOT/examples/suit_update/bin/samr21-xpro/suit_update-riot.suitv4_signed.1557135946.bin"
|
published "/home/francisco/workspace/RIOT/examples/suit_update/bin/samr21-xpro/suit_update-riot.suitv3_signed.1557135946.bin"
|
||||||
as "coap://[2001:db8::1]/fw/samr21-xpro/suit_update-riot.suitv4_signed.1557135946.bin"
|
as "coap://[2001:db8::1]/fw/samr21-xpro/suit_update-riot.suitv3_signed.1557135946.bin"
|
||||||
published "/home/francisco/workspace/RIOT/examples/suit_update/bin/samr21-xpro/suit_update-riot.suitv4_signed.latest.bin"
|
published "/home/francisco/workspace/RIOT/examples/suit_update/bin/samr21-xpro/suit_update-riot.suitv3_signed.latest.bin"
|
||||||
as "coap://[2001:db8::1]/fw/samr21-xpro/suit_update-riot.suitv4_signed.latest.bin"
|
as "coap://[2001:db8::1]/fw/samr21-xpro/suit_update-riot.suitv3_signed.latest.bin"
|
||||||
...
|
...
|
||||||
|
|
||||||
### Notify an update to the device
|
### Notify an update to the device
|
||||||
[update-notify]: #Norify-an-update-to-the-device
|
[update-notify]: #Norify-an-update-to-the-device
|
||||||
|
|
||||||
If the network has been started with a standalone node, the RIOT node should be
|
If the network has been started with a standalone node, the RIOT node should be
|
||||||
reachable via link-local `fe80::2%riot0` on the ethos interface. If it was setup as a
|
reachable via link-local EUI64 address on the ethos interface, e.g:
|
||||||
wireless device it will be reachable via its global address, something like `2001:db8::7b7e:3255:1313:8d96`
|
|
||||||
|
|
||||||
|
Iface 5 HWaddr: 02:BE:74:C0:2F:B9
|
||||||
|
L2-PDU:1500 MTU:1500 HL:64 RTR
|
||||||
|
RTR_ADV
|
||||||
|
Source address length: 6
|
||||||
|
Link type: wired
|
||||||
|
inet6 addr: fe80::7b7e:3255:1313:8d96 scope: link VAL
|
||||||
|
inet6 addr: fe80::2 scope: link VAL
|
||||||
|
inet6 group: ff02::2
|
||||||
|
inet6 group: ff02::1
|
||||||
|
inet6 group: ff02::1:ffc0:2fb9
|
||||||
|
inet6 group: ff02::1:ff00:2
|
||||||
|
|
||||||
|
the EUI64 link local address is `fe80::7b7e:3255:1313:8d96` and
|
||||||
|
SUIT_CLIENT=[fe80::7b7e:3255:1313:8d96%riot0].
|
||||||
|
|
||||||
|
If it was setup as a wireless device it will be reachable via its global
|
||||||
|
address, e.g:
|
||||||
|
|
||||||
|
|
||||||
|
Iface 6 HWaddr: 0D:96 Channel: 26 Page: 0 NID: 0x23
|
||||||
|
Long HWaddr: 79:7E:32:55:13:13:8D:96
|
||||||
|
TX-Power: 0dBm State: IDLE max. Retrans.: 3 CSMA Retries: 4
|
||||||
|
AUTOACK ACK_REQ CSMA L2-PDU:102 MTU:1280 HL:64 RTR
|
||||||
|
RTR_ADV 6LO IPHC
|
||||||
|
Source address length: 8
|
||||||
|
Link type: wireless
|
||||||
|
inet6 addr: fe80::7b7e:3255:1313:8d96 scope: link VAL
|
||||||
|
inet6 addr: 2001:db8::7b7e:3255:1313:8d96 scope: global VAL
|
||||||
|
inet6 group: ff02::2
|
||||||
|
inet6 group: ff02::1
|
||||||
|
inet6 group: ff02::1:ff17:dd59
|
||||||
|
inet6 group: ff02::1:ff00:2
|
||||||
|
|
||||||
|
|
||||||
|
the global address is `2001:db8::7b7e:3255:1313:8d96` and
|
||||||
|
SUIT_CLIENT=[2001:db8::7b7e:3255:1313:8d96].
|
||||||
|
|
||||||
- In wired mode:
|
- In wired mode:
|
||||||
|
|
||||||
$ SUIT_COAP_SERVER=[2001:db8::1] SUIT_CLIENT=[fe80::2%riot0] BOARD=samr21-xpro make -C examples/suit_update suit/notify
|
$ SUIT_COAP_SERVER=[2001:db8::1] SUIT_CLIENT=[fe80::7b7e:3255:1313:8d96%riot] BOARD=samr21-xpro make -C examples/suit_update suit/notify
|
||||||
|
|
||||||
- In wireless mode:
|
- In wireless mode:
|
||||||
|
|
||||||
@ -394,16 +431,12 @@ wireless device it will be reachable via its global address, something like `200
|
|||||||
This notifies the node of a new available manifest. Once the notification is
|
This notifies the node of a new available manifest. Once the notification is
|
||||||
received by the device, it fetches it.
|
received by the device, it fetches it.
|
||||||
|
|
||||||
If using `suit-v4` the node hangs for a couple of seconds when verifying the
|
If using `suit-v3` the node hangs for a couple of seconds when verifying the
|
||||||
signature:
|
signature:
|
||||||
|
|
||||||
....
|
....
|
||||||
INFO # suit_coap: got manifest with size 545
|
suit_coap: got manifest with size 470
|
||||||
INFO # jumping into map
|
suit: verifying manifest signature
|
||||||
INFO # )got key val=1
|
|
||||||
INFO # handler res=0
|
|
||||||
INFO # got key val=2
|
|
||||||
INFO # suit: verifying manifest signature...
|
|
||||||
....
|
....
|
||||||
|
|
||||||
Once the signature is validated it continues validating other parts of the
|
Once the signature is validated it continues validating other parts of the
|
||||||
@ -412,12 +445,12 @@ Among these validations it checks some condition like firmware offset position
|
|||||||
in regards to the running slot to see witch firmware image to fetch.
|
in regards to the running slot to see witch firmware image to fetch.
|
||||||
|
|
||||||
....
|
....
|
||||||
INFO # Handling handler with key 10 at 0x2b981
|
suit: validated manifest version
|
||||||
INFO # Comparing manifest offset 4096 with other slot offset 4096
|
)suit: validated sequence number
|
||||||
....
|
)validating vendor ID
|
||||||
INFO # Handling handler with key 10 at 0x2b981
|
Comparing 547d0d74-6d3a-5a92-9662-4881afd9407b to 547d0d74-6d3a-5a92-9662-4881afd9407b from manifest
|
||||||
INFO # Comparing manifest offset 133120 with other slot offset 4096
|
validating vendor ID: OK
|
||||||
INFO # Sequence handler error
|
validating class id
|
||||||
....
|
....
|
||||||
|
|
||||||
Once the manifest validation is complete, the application fetches the image
|
Once the manifest validation is complete, the application fetches the image
|
||||||
@ -432,27 +465,22 @@ displayed during this step:
|
|||||||
Once the new image is written, a final validation is performed and, in case of
|
Once the new image is written, a final validation is performed and, in case of
|
||||||
success, the application reboots on the new slot:
|
success, the application reboots on the new slot:
|
||||||
|
|
||||||
2019-04-05 16:19:26,363 - INFO # riotboot: verifying digest at 0x20003f37 (img at: 0x20800 size: 80212)
|
Verifying image digest
|
||||||
2019-04-05 16:19:26,704 - INFO # handler res=0
|
riotboot: verifying digest at 0x1fffbd15 (img at: 0x1000 size: 77448)
|
||||||
2019-04-05 16:19:26,705 - INFO # got key val=10
|
Verifying image digest
|
||||||
2019-04-05 16:19:26,707 - INFO # no handler found
|
riotboot: verifying digest at 0x1fffbd15 (img at: 0x1000 size: 77448)
|
||||||
2019-04-05 16:19:26,708 - INFO # got key val=12
|
suit_parse() success
|
||||||
2019-04-05 16:19:26,709 - INFO # no handler found
|
SUIT policy check OK.
|
||||||
2019-04-05 16:19:26,711 - INFO # handler res=0
|
suit_coap: finalizing image flash
|
||||||
2019-04-05 16:19:26,713 - INFO # suit_v4_parse() success
|
riotboot_flashwrite: riotboot flashing completed successfully
|
||||||
2019-04-05 16:19:26,715 - INFO # SUIT policy check OK.
|
Image magic_number: 0x544f4952
|
||||||
2019-04-05 16:19:26,718 - INFO # suit_coap: finalizing image flash
|
Image Version: 0x5e71f662
|
||||||
2019-04-05 16:19:26,725 - INFO # riotboot_flashwrite: riotboot flashing completed successfully
|
Image start address: 0x00001100
|
||||||
2019-04-05 16:19:26,728 - INFO # Image magic_number: 0x544f4952
|
Header chksum: 0x745a0376
|
||||||
2019-04-05 16:19:26,730 - INFO # Image Version: 0x5ca76390
|
|
||||||
2019-04-05 16:19:26,733 - INFO # Image start address: 0x00020900
|
|
||||||
2019-04-05 16:19:26,738 - INFO # Header chksum: 0x13b466db
|
|
||||||
|
|
||||||
|
main(): This is RIOT! (Version: 2020.04)
|
||||||
main(): This is RIOT! (Version: 2019.04-devel-606-gaa7b-ota_suit_v2)
|
|
||||||
RIOT SUIT update example application
|
RIOT SUIT update example application
|
||||||
running from slot 1
|
running from slot 1
|
||||||
Waiting for address autoconfiguration...
|
|
||||||
|
|
||||||
The slot number should have changed from after the application reboots.
|
The slot number should have changed from after the application reboots.
|
||||||
You can do the publish-notify sequence several times to verify this.
|
You can do the publish-notify sequence several times to verify this.
|
||||||
@ -466,11 +494,9 @@ For the suit_update to work there are important modules that aren't normally bui
|
|||||||
in a RIOT application:
|
in a RIOT application:
|
||||||
|
|
||||||
* riotboot
|
* riotboot
|
||||||
* riotboot_hdr
|
* riotboot_flashwrite
|
||||||
* riotboot_slot
|
|
||||||
* suit
|
* suit
|
||||||
* suit_coap
|
* suit_transport_coap
|
||||||
* suit_v4
|
|
||||||
|
|
||||||
#### riotboot
|
#### riotboot
|
||||||
|
|
||||||
@ -492,21 +518,21 @@ The flash memory will be divided in the following way:
|
|||||||
The riotboot part of the flash will not be changed during suit_updates but
|
The riotboot part of the flash will not be changed during suit_updates but
|
||||||
be flashed a first time with at least one slot with suit_capable fw.
|
be flashed a first time with at least one slot with suit_capable fw.
|
||||||
|
|
||||||
$ BOARD=samr21-xpro make -C examples/suit_update clean riotboot/flash
|
$ BOARD=samr21-xpro make -C examples/suit_update clean flash
|
||||||
|
|
||||||
When calling make with the riotboot/flash argument it will flash the bootloader
|
When calling make with the `flash` argument it will flash the bootloader
|
||||||
and then to slot0 a copy of the firmware you intend to build.
|
and then to slot0 a copy of the firmware you intend to build.
|
||||||
|
|
||||||
New images must be of course written to the inactive slot, the device mist be able
|
New images must be of course written to the inactive slot, the device mist be able
|
||||||
to boot from the previous image in case the update had some kind of error, eg:
|
to boot from the previous image in case the update had some kind of error, eg:
|
||||||
the image corresponds to the wrong slot.
|
the image corresponds to the wrong slot.
|
||||||
|
|
||||||
The active/inactive coap resources is used so the publisher can send a manifest
|
On boot the bootloader will check the `riotboot_hdr` and boot on the newest
|
||||||
built for the inactive slot.
|
|
||||||
|
|
||||||
On boot the bootloader will check the riotboot_hdr and boot on the newest
|
|
||||||
image.
|
image.
|
||||||
|
|
||||||
|
`riotboot_flashwrite` module is needed to be able to write the new firmware to
|
||||||
|
the inactive slot.
|
||||||
|
|
||||||
riotboot is not supported by all boards. The default board is `samr21-xpro`,
|
riotboot is not supported by all boards. The default board is `samr21-xpro`,
|
||||||
but any board supporting `riotboot`, `flashpage` and with 256kB of flash should
|
but any board supporting `riotboot`, `flashpage` and with 256kB of flash should
|
||||||
be able to run the demo.
|
be able to run the demo.
|
||||||
@ -516,7 +542,7 @@ be able to run the demo.
|
|||||||
The suit module encloses all the other suit_related module. Formally this only
|
The suit module encloses all the other suit_related module. Formally this only
|
||||||
includes the `sys/suit` directory into the build system dirs.
|
includes the `sys/suit` directory into the build system dirs.
|
||||||
|
|
||||||
- **suit_coap**
|
- **suit_transport_coap**
|
||||||
|
|
||||||
To enable support for suit_updates over coap a new thread is created.
|
To enable support for suit_updates over coap a new thread is created.
|
||||||
This thread will expose 4 suit related resources:
|
This thread will expose 4 suit related resources:
|
||||||
@ -533,9 +559,9 @@ When a new manifest url is received on the trigger resource a message is resent
|
|||||||
to the coap thread with the manifest's url. The thread will then fetch the
|
to the coap thread with the manifest's url. The thread will then fetch the
|
||||||
manifest by a block coap request to the specified url.
|
manifest by a block coap request to the specified url.
|
||||||
|
|
||||||
- **support for v4**
|
- **support for v3**
|
||||||
|
|
||||||
This includes v4 manifest support. When a url is received in the /suit/trigger
|
This includes v3 manifest support. When a url is received in the /suit/trigger
|
||||||
coap resource it will trigger a coap blockwise fetch of the manifest. When this
|
coap resource it will trigger a coap blockwise fetch of the manifest. When this
|
||||||
manifest is received it will be parsed. The signature of the manifest will be
|
manifest is received it will be parsed. The signature of the manifest will be
|
||||||
verified and then the rest of the manifest content. If the received manifest is valid it
|
verified and then the rest of the manifest content. If the received manifest is valid it
|
||||||
@ -610,20 +636,19 @@ The following variables are defined in makefiles/suit.inc.mk:
|
|||||||
|
|
||||||
The following convention is used when naming a manifest
|
The following convention is used when naming a manifest
|
||||||
|
|
||||||
SUIT_MANIFEST ?= $(BINDIR_APP)-riot.suitv4.$(APP_VER).bin
|
SUIT_MANIFEST ?= $(BINDIR_APP)-riot.suitv3.$(APP_VER).bin
|
||||||
SUIT_MANIFEST_LATEST ?= $(BINDIR_APP)-riot.suitv4.latest.bin
|
SUIT_MANIFEST_LATEST ?= $(BINDIR_APP)-riot.suitv3.latest.bin
|
||||||
SUIT_MANIFEST_SIGNED ?= $(BINDIR_APP)-riot.suitv4_signed.$(APP_VER).bin
|
SUIT_MANIFEST_SIGNED ?= $(BINDIR_APP)-riot.suitv3_signed.$(APP_VER).bin
|
||||||
SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_APP)-riot.suitv4_signed.latest.bin
|
SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_APP)-riot.suitv3_signed.latest.bin
|
||||||
|
|
||||||
The following default values are using for generating the manifest:
|
The following default values are using for generating the manifest:
|
||||||
|
|
||||||
SUIT_VENDOR ?= RIOT
|
SUIT_VENDOR ?= "riot-os.org"
|
||||||
SUIT_VERSION ?= $(APP_VER)
|
SUIT_SEQNR ?= $(APP_VER)
|
||||||
SUIT_CLASS ?= $(BOARD)
|
SUIT_CLASS ?= $(BOARD)
|
||||||
SUIT_KEY ?= default
|
SUIT_KEY ?= default
|
||||||
SUIT_KEY_DIR ?= $(RIOTBASE)/keys
|
SUIT_KEY_DIR ?= $(RIOTBASE)/keys
|
||||||
SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY)
|
SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pem
|
||||||
SUIT_PUB ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pub
|
|
||||||
|
|
||||||
All files (both slot binaries, both manifests, copies of manifests with
|
All files (both slot binaries, both manifests, copies of manifests with
|
||||||
"latest" instead of `$APP_VER` in riotboot build) are copied into the folder
|
"latest" instead of `$APP_VER` in riotboot build) are copied into the folder
|
||||||
@ -640,7 +665,7 @@ The following recipes are defined in makefiles/suit.inc.mk:
|
|||||||
suit/manifest: creates a non signed and signed manifest, and also a latest tag for these.
|
suit/manifest: creates a non signed and signed manifest, and also a latest tag for these.
|
||||||
It uses following parameters:
|
It uses following parameters:
|
||||||
|
|
||||||
- $(SUIT_KEY): name of keypair to sign the manifest
|
- $(SUIT_KEY): name of key to sign the manifest
|
||||||
- $(SUIT_COAP_ROOT): coap root address
|
- $(SUIT_COAP_ROOT): coap root address
|
||||||
- $(SUIT_CLASS)
|
- $(SUIT_CLASS)
|
||||||
- $(SUIT_VERSION)
|
- $(SUIT_VERSION)
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "net/nanocoap.h"
|
#include "net/nanocoap.h"
|
||||||
#include "suit/coap.h"
|
#include "suit/transport/coap.h"
|
||||||
|
|
||||||
static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context)
|
static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
#include "shell.h"
|
#include "shell.h"
|
||||||
|
|
||||||
#include "suit/coap.h"
|
#include "suit/transport/coap.h"
|
||||||
#include "riotboot/slot.h"
|
#include "riotboot/slot.h"
|
||||||
|
|
||||||
#ifdef MODULE_PERIPH_GPIO
|
#ifdef MODULE_PERIPH_GPIO
|
||||||
|
|||||||
@ -138,7 +138,7 @@ def _test_invalid_version(child, client, app_ver):
|
|||||||
publish(TMPDIR.name, COAP_HOST, app_ver - 1)
|
publish(TMPDIR.name, COAP_HOST, app_ver - 1)
|
||||||
notify(COAP_HOST, client, app_ver - 1)
|
notify(COAP_HOST, client, app_ver - 1)
|
||||||
child.expect_exact("suit_coap: trigger received")
|
child.expect_exact("suit_coap: trigger received")
|
||||||
child.expect_exact("suit: verifying manifest signature...")
|
child.expect_exact("suit: verifying manifest signature")
|
||||||
child.expect_exact("seq_nr <= running image")
|
child.expect_exact("seq_nr <= running image")
|
||||||
|
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ def _test_invalid_signature(child, client, app_ver):
|
|||||||
publish(TMPDIR.name, COAP_HOST, app_ver + 1, 'invalid_keys')
|
publish(TMPDIR.name, COAP_HOST, app_ver + 1, 'invalid_keys')
|
||||||
notify(COAP_HOST, client, app_ver + 1)
|
notify(COAP_HOST, client, app_ver + 1)
|
||||||
child.expect_exact("suit_coap: trigger received")
|
child.expect_exact("suit_coap: trigger received")
|
||||||
child.expect_exact("suit: verifying manifest signature...")
|
child.expect_exact("suit: verifying manifest signature")
|
||||||
child.expect_exact("Unable to validate signature")
|
child.expect_exact("Unable to validate signature")
|
||||||
|
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ def _test_successful_update(child, client, app_ver):
|
|||||||
publish(TMPDIR.name, COAP_HOST, version)
|
publish(TMPDIR.name, COAP_HOST, version)
|
||||||
notify(COAP_HOST, client, version)
|
notify(COAP_HOST, client, version)
|
||||||
child.expect_exact("suit_coap: trigger received")
|
child.expect_exact("suit_coap: trigger received")
|
||||||
child.expect_exact("suit: verifying manifest signature...")
|
child.expect_exact("suit: verifying manifest signature")
|
||||||
child.expect(
|
child.expect(
|
||||||
r"riotboot_flashwrite: initializing update to target slot (\d+)\r\n",
|
r"riotboot_flashwrite: initializing update to target slot (\d+)\r\n",
|
||||||
timeout=MANIFEST_TIMEOUT,
|
timeout=MANIFEST_TIMEOUT,
|
||||||
|
|||||||
@ -146,8 +146,8 @@ riotboot/flash: riotboot/flash-slot0 riotboot/flash-bootloader
|
|||||||
FLASHFILE = $(RIOTBOOT_EXTENDED_BIN)
|
FLASHFILE = $(RIOTBOOT_EXTENDED_BIN)
|
||||||
|
|
||||||
# include suit targets
|
# include suit targets
|
||||||
ifneq (,$(filter suit_v4, $(USEMODULE)))
|
ifneq (,$(filter suit, $(USEMODULE)))
|
||||||
include $(RIOTMAKE)/suit.v4.inc.mk
|
include $(RIOTMAKE)/suit.inc.mk
|
||||||
endif
|
endif
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@ -96,7 +96,7 @@ PSEUDOMODULES += stdin
|
|||||||
PSEUDOMODULES += stdio_ethos
|
PSEUDOMODULES += stdio_ethos
|
||||||
PSEUDOMODULES += stdio_cdc_acm
|
PSEUDOMODULES += stdio_cdc_acm
|
||||||
PSEUDOMODULES += stdio_uart_rx
|
PSEUDOMODULES += stdio_uart_rx
|
||||||
PSEUDOMODULES += suit_%
|
PSEUDOMODULES += suit_transport_%
|
||||||
PSEUDOMODULES += wakaama_objects_%
|
PSEUDOMODULES += wakaama_objects_%
|
||||||
PSEUDOMODULES += zptr
|
PSEUDOMODULES += zptr
|
||||||
PSEUDOMODULES += ztimer%
|
PSEUDOMODULES += ztimer%
|
||||||
@ -104,9 +104,6 @@ PSEUDOMODULES += ztimer%
|
|||||||
# ztimer's main module is called "ztimer_core"
|
# ztimer's main module is called "ztimer_core"
|
||||||
NO_PSEUDOMODULES += ztimer_core
|
NO_PSEUDOMODULES += ztimer_core
|
||||||
|
|
||||||
# handle suit_v4 being a distinct module
|
|
||||||
NO_PSEUDOMODULES += suit_v4
|
|
||||||
|
|
||||||
# print ascii representation in function od_hex_dump()
|
# print ascii representation in function od_hex_dump()
|
||||||
PSEUDOMODULES += od_string
|
PSEUDOMODULES += od_string
|
||||||
|
|
||||||
|
|||||||
40
makefiles/suit.base.inc.mk
Normal file
40
makefiles/suit.base.inc.mk
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#
|
||||||
|
# path to suit-tool
|
||||||
|
SUIT_TOOL ?= $(RIOTBASE)/dist/tools/suit_v3/suit-manifest-generator/bin/suit-tool
|
||||||
|
|
||||||
|
#
|
||||||
|
# SUIT encryption keys
|
||||||
|
#
|
||||||
|
|
||||||
|
# Specify key to use.
|
||||||
|
# Will use $(SUIT_KEY_DIR)/$(SUIT_KEY).pem as combined private/public key
|
||||||
|
# files.
|
||||||
|
SUIT_KEY ?= default
|
||||||
|
|
||||||
|
ifeq (1, $(RIOT_CI_BUILD))
|
||||||
|
SUIT_KEY_DIR ?= $(BINDIR)
|
||||||
|
else
|
||||||
|
SUIT_KEY_DIR ?= $(RIOTBASE)/keys
|
||||||
|
endif
|
||||||
|
|
||||||
|
SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pem
|
||||||
|
|
||||||
|
SUIT_PUB_HDR = $(BINDIR)/riotbuild/public_key.h
|
||||||
|
SUIT_PUB_HDR_DIR = $(dir $(SUIT_PUB_HDR))
|
||||||
|
CFLAGS += -I$(SUIT_PUB_HDR_DIR)
|
||||||
|
BUILDDEPS += $(SUIT_PUB_HDR)
|
||||||
|
|
||||||
|
$(SUIT_SEC): $(CLEAN)
|
||||||
|
@echo suit: generating key in $(SUIT_KEY_DIR)
|
||||||
|
@mkdir -p $(SUIT_KEY_DIR)
|
||||||
|
@$(RIOTBASE)/dist/tools/suit_v3/gen_key.py $(SUIT_SEC)
|
||||||
|
|
||||||
|
# set FORCE so switching between keys using "SUIT_KEY=foo make ..."
|
||||||
|
# triggers a rebuild even if the new key would otherwise not (because the other
|
||||||
|
# key's mtime is too far back).
|
||||||
|
$(SUIT_PUB_HDR): $(SUIT_SEC) FORCE | $(CLEAN)
|
||||||
|
@mkdir -p $(SUIT_PUB_HDR_DIR)
|
||||||
|
@$(SUIT_TOOL) pubkey -k $(SUIT_SEC) \
|
||||||
|
| '$(LAZYSPONGE)' $(LAZYSPONGE_FLAGS) '$@'
|
||||||
|
|
||||||
|
suit/genkey: $(SUIT_SEC)
|
||||||
72
makefiles/suit.inc.mk
Normal file
72
makefiles/suit.inc.mk
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#
|
||||||
|
# This file contains stuff related to SUIT manifest generation.
|
||||||
|
# It depends on SUIT key generation, which can be found in
|
||||||
|
# makefiles/suit.base.inc.mk
|
||||||
|
#
|
||||||
|
#
|
||||||
|
SUIT_COAP_BASEPATH ?= fw/$(BOARD)
|
||||||
|
SUIT_COAP_SERVER ?= localhost
|
||||||
|
SUIT_COAP_ROOT ?= coap://$(SUIT_COAP_SERVER)/$(SUIT_COAP_BASEPATH)
|
||||||
|
SUIT_COAP_FSROOT ?= $(RIOTBASE)/coaproot
|
||||||
|
|
||||||
|
#
|
||||||
|
SUIT_MANIFEST ?= $(BINDIR_APP)-riot.suitv3.$(APP_VER).bin
|
||||||
|
SUIT_MANIFEST_LATEST ?= $(BINDIR_APP)-riot.suitv3.latest.bin
|
||||||
|
SUIT_MANIFEST_SIGNED ?= $(BINDIR_APP)-riot.suitv3_signed.$(APP_VER).bin
|
||||||
|
SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_APP)-riot.suitv3_signed.latest.bin
|
||||||
|
|
||||||
|
SUIT_NOTIFY_VERSION ?= latest
|
||||||
|
SUIT_NOTIFY_MANIFEST ?= $(APPLICATION)-riot.suitv3_signed.$(SUIT_NOTIFY_VERSION).bin
|
||||||
|
|
||||||
|
# Long manifest names require more buffer space when parsing
|
||||||
|
export CFLAGS += -DCONFIG_SOCK_URLPATH_MAXLEN=128
|
||||||
|
|
||||||
|
SUIT_VENDOR ?= "riot-os.org"
|
||||||
|
SUIT_SEQNR ?= $(APP_VER)
|
||||||
|
SUIT_CLASS ?= $(BOARD)
|
||||||
|
|
||||||
|
#
|
||||||
|
$(SUIT_MANIFEST): $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN)
|
||||||
|
$(RIOTBASE)/dist/tools/suit_v3/gen_manifest.py \
|
||||||
|
--urlroot $(SUIT_COAP_ROOT) \
|
||||||
|
--seqnr $(SUIT_SEQNR) \
|
||||||
|
--uuid-vendor $(SUIT_VENDOR) \
|
||||||
|
--uuid-class $(SUIT_CLASS) \
|
||||||
|
-o $@.tmp \
|
||||||
|
$(SLOT0_RIOT_BIN):$(SLOT0_OFFSET) \
|
||||||
|
$(SLOT1_RIOT_BIN):$(SLOT1_OFFSET)
|
||||||
|
|
||||||
|
$(SUIT_TOOL) create -f suit -i $@.tmp -o $@
|
||||||
|
|
||||||
|
rm -f $@.tmp
|
||||||
|
|
||||||
|
|
||||||
|
$(SUIT_MANIFEST_SIGNED): $(SUIT_MANIFEST) $(SUIT_SEC)
|
||||||
|
$(SUIT_TOOL) sign -k $(SUIT_SEC) -m $(SUIT_MANIFEST) -o $@
|
||||||
|
|
||||||
|
$(SUIT_MANIFEST_LATEST): $(SUIT_MANIFEST)
|
||||||
|
@ln -f -s $< $@
|
||||||
|
|
||||||
|
$(SUIT_MANIFEST_SIGNED_LATEST): $(SUIT_MANIFEST_SIGNED)
|
||||||
|
@ln -f -s $< $@
|
||||||
|
|
||||||
|
SUIT_MANIFESTS := $(SUIT_MANIFEST) \
|
||||||
|
$(SUIT_MANIFEST_LATEST) \
|
||||||
|
$(SUIT_MANIFEST_SIGNED) \
|
||||||
|
$(SUIT_MANIFEST_SIGNED_LATEST)
|
||||||
|
|
||||||
|
suit/manifest: $(SUIT_MANIFESTS)
|
||||||
|
|
||||||
|
suit/publish: $(SUIT_MANIFESTS) $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN)
|
||||||
|
@mkdir -p $(SUIT_COAP_FSROOT)/$(SUIT_COAP_BASEPATH)
|
||||||
|
@cp $^ $(SUIT_COAP_FSROOT)/$(SUIT_COAP_BASEPATH)
|
||||||
|
@for file in $^; do \
|
||||||
|
echo "published \"$$file\""; \
|
||||||
|
echo " as \"$(SUIT_COAP_ROOT)/$$(basename $$file)\""; \
|
||||||
|
done
|
||||||
|
|
||||||
|
suit/notify: | $(filter suit/publish, $(MAKECMDGOALS))
|
||||||
|
@test -n "$(SUIT_CLIENT)" || { echo "error: SUIT_CLIENT unset!"; false; }
|
||||||
|
aiocoap-client -m POST "coap://$(SUIT_CLIENT)/suit/trigger" \
|
||||||
|
--payload "$(SUIT_COAP_ROOT)/$(SUIT_NOTIFY_MANIFEST)" && \
|
||||||
|
echo "Triggered $(SUIT_CLIENT) to update."
|
||||||
@ -1,103 +0,0 @@
|
|||||||
#
|
|
||||||
SUIT_COAP_BASEPATH ?= fw/$(BOARD)
|
|
||||||
SUIT_COAP_SERVER ?= localhost
|
|
||||||
SUIT_COAP_ROOT ?= coap://$(SUIT_COAP_SERVER)/$(SUIT_COAP_BASEPATH)
|
|
||||||
SUIT_COAP_FSROOT ?= $(RIOTBASE)/coaproot
|
|
||||||
|
|
||||||
#
|
|
||||||
SUIT_MANIFEST ?= $(BINDIR_APP)-riot.suitv4.$(APP_VER).bin
|
|
||||||
SUIT_MANIFEST_LATEST ?= $(BINDIR_APP)-riot.suitv4.latest.bin
|
|
||||||
SUIT_MANIFEST_SIGNED ?= $(BINDIR_APP)-riot.suitv4_signed.$(APP_VER).bin
|
|
||||||
SUIT_MANIFEST_SIGNED_LATEST ?= $(BINDIR_APP)-riot.suitv4_signed.latest.bin
|
|
||||||
|
|
||||||
SUIT_NOTIFY_VERSION ?= latest
|
|
||||||
SUIT_NOTIFY_MANIFEST ?= $(APPLICATION)-riot.suitv4_signed.$(SUIT_NOTIFY_VERSION).bin
|
|
||||||
|
|
||||||
# Long manifest names require more buffer space when parsing
|
|
||||||
export CFLAGS += -DCONFIG_SOCK_URLPATH_MAXLEN=128
|
|
||||||
|
|
||||||
SUIT_VENDOR ?= "riot-os.org"
|
|
||||||
SUIT_SEQNR ?= $(APP_VER)
|
|
||||||
SUIT_CLASS ?= $(BOARD)
|
|
||||||
|
|
||||||
#
|
|
||||||
# SUIT encryption keys
|
|
||||||
#
|
|
||||||
|
|
||||||
# Specify key to use.
|
|
||||||
# Will use $(SUIT_KEY_DIR)/$(SUIT_KEY) $(SUIT_KEY_DIR)/$(SUIT_KEY).pub as
|
|
||||||
# private/public key files, similar to how ssh names its key files.
|
|
||||||
SUIT_KEY ?= default
|
|
||||||
|
|
||||||
ifeq (1, $(RIOT_CI_BUILD))
|
|
||||||
SUIT_KEY_DIR ?= $(BINDIR)
|
|
||||||
else
|
|
||||||
SUIT_KEY_DIR ?= $(RIOTBASE)/keys
|
|
||||||
endif
|
|
||||||
|
|
||||||
SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY)
|
|
||||||
SUIT_PUB ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pub
|
|
||||||
|
|
||||||
SUIT_PUB_HDR = $(BINDIR)/riotbuild/public_key.h
|
|
||||||
SUIT_PUB_HDR_DIR = $(dir $(SUIT_PUB_HDR))
|
|
||||||
CFLAGS += -I$(SUIT_PUB_HDR_DIR)
|
|
||||||
BUILDDEPS += $(SUIT_PUB_HDR)
|
|
||||||
|
|
||||||
$(SUIT_SEC) $(SUIT_PUB): $(CLEAN)
|
|
||||||
@echo suit: generating key pair in $(SUIT_KEY_DIR)
|
|
||||||
@mkdir -p $(SUIT_KEY_DIR)
|
|
||||||
@$(RIOTBASE)/dist/tools/suit_v4/gen_key.py $(SUIT_SEC) $(SUIT_PUB)
|
|
||||||
|
|
||||||
# set FORCE so switching between keys using "SUIT_KEY=foo make ..."
|
|
||||||
# triggers a rebuild even if the new key would otherwise not (because the other
|
|
||||||
# key's mtime is too far back).
|
|
||||||
$(SUIT_PUB_HDR): $(SUIT_PUB) FORCE | $(CLEAN)
|
|
||||||
@mkdir -p $(SUIT_PUB_HDR_DIR)
|
|
||||||
@cp $(SUIT_PUB) $(SUIT_PUB_HDR_DIR)/public.key
|
|
||||||
@cd $(SUIT_PUB_HDR_DIR) && xxd -i public.key \
|
|
||||||
| '$(LAZYSPONGE)' $(LAZYSPONGE_FLAGS) '$@'
|
|
||||||
|
|
||||||
suit/genkey: $(SUIT_SEC) $(SUIT_PUB)
|
|
||||||
|
|
||||||
#
|
|
||||||
$(SUIT_MANIFEST): $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN)
|
|
||||||
$(RIOTBASE)/dist/tools/suit_v4/gen_manifest.py \
|
|
||||||
--template $(RIOTBASE)/dist/tools/suit_v4/test-2img.json \
|
|
||||||
--urlroot $(SUIT_COAP_ROOT) \
|
|
||||||
--seqnr $(SUIT_SEQNR) \
|
|
||||||
--uuid-vendor $(SUIT_VENDOR) \
|
|
||||||
--uuid-class $(SUIT_CLASS) \
|
|
||||||
--offsets $(SLOT0_OFFSET),$(SLOT1_OFFSET) \
|
|
||||||
-o $@ \
|
|
||||||
$^
|
|
||||||
|
|
||||||
$(SUIT_MANIFEST_SIGNED): $(SUIT_MANIFEST) $(SUIT_SEC) $(SUIT_PUB)
|
|
||||||
$(RIOTBASE)/dist/tools/suit_v4/sign-04.py \
|
|
||||||
$(SUIT_SEC) $(SUIT_PUB) $< $@
|
|
||||||
|
|
||||||
$(SUIT_MANIFEST_LATEST): $(SUIT_MANIFEST)
|
|
||||||
@ln -f -s $< $@
|
|
||||||
|
|
||||||
$(SUIT_MANIFEST_SIGNED_LATEST): $(SUIT_MANIFEST_SIGNED)
|
|
||||||
@ln -f -s $< $@
|
|
||||||
|
|
||||||
SUIT_MANIFESTS := $(SUIT_MANIFEST) \
|
|
||||||
$(SUIT_MANIFEST_LATEST) \
|
|
||||||
$(SUIT_MANIFEST_SIGNED) \
|
|
||||||
$(SUIT_MANIFEST_SIGNED_LATEST)
|
|
||||||
|
|
||||||
suit/manifest: $(SUIT_MANIFESTS)
|
|
||||||
|
|
||||||
suit/publish: $(SUIT_MANIFESTS) $(SLOT0_RIOT_BIN) $(SLOT1_RIOT_BIN)
|
|
||||||
@mkdir -p $(SUIT_COAP_FSROOT)/$(SUIT_COAP_BASEPATH)
|
|
||||||
@cp $^ $(SUIT_COAP_FSROOT)/$(SUIT_COAP_BASEPATH)
|
|
||||||
@for file in $^; do \
|
|
||||||
echo "published \"$$file\""; \
|
|
||||||
echo " as \"$(SUIT_COAP_ROOT)/$$(basename $$file)\""; \
|
|
||||||
done
|
|
||||||
|
|
||||||
suit/notify: | $(filter suit/publish, $(MAKECMDGOALS))
|
|
||||||
@test -n "$(SUIT_CLIENT)" || { echo "error: SUIT_CLIENT unset!"; false; }
|
|
||||||
aiocoap-client -m POST "coap://$(SUIT_CLIENT)/suit/trigger" \
|
|
||||||
--payload "$(SUIT_COAP_ROOT)/$(SUIT_NOTIFY_MANIFEST)" && \
|
|
||||||
echo "Triggered $(SUIT_CLIENT) to update."
|
|
||||||
@ -7,10 +7,18 @@
|
|||||||
* directory for more details.
|
* directory for more details.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @defgroup sys_suit_v4 SUIT draft v4
|
* @defgroup sys_suit SUIT secure firmware OTA upgrade infrastructure
|
||||||
* @ingroup sys_suit
|
* @ingroup sys
|
||||||
* @brief SUIT manifest handling
|
* @brief SUIT manifest handling
|
||||||
*
|
*
|
||||||
|
* @experimental
|
||||||
|
*
|
||||||
|
* @note The current implementation of this specification is based on the
|
||||||
|
* IETF-SUIT-v3 draft. The module is still experimental and will change to
|
||||||
|
* match future draft specifications
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/draft-ietf-suit-manifest-03
|
||||||
|
*
|
||||||
* @{
|
* @{
|
||||||
*
|
*
|
||||||
* @brief Handler functions for SUIT manifests
|
* @brief Handler functions for SUIT manifests
|
||||||
@ -19,8 +27,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SUIT_V4_SUIT_H
|
#ifndef SUIT_H
|
||||||
#define SUIT_V4_SUIT_H
|
#define SUIT_H
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -38,27 +46,32 @@ extern "C" {
|
|||||||
* @brief Buffer size used for Cose
|
* @brief Buffer size used for Cose
|
||||||
*/
|
*/
|
||||||
#ifndef SUIT_COSE_BUF_SIZE
|
#ifndef SUIT_COSE_BUF_SIZE
|
||||||
#define SUIT_COSE_BUF_SIZE (512U)
|
#define SUIT_COSE_BUF_SIZE (180U)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Maximum number of components used for SUIT v4
|
* @brief Maximum number of components supported in a SUIT manifest
|
||||||
*/
|
*/
|
||||||
#define SUIT_V4_COMPONENT_MAX (1U)
|
#define SUIT_COMPONENT_MAX (1U)
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Supported SUIT manifest version
|
|
||||||
*/
|
|
||||||
#define SUIT_MANIFEST_VERSION (4)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Current SUIT serialization format version
|
* @brief Current SUIT serialization format version
|
||||||
*
|
*
|
||||||
* see https://tools.ietf.org/html/draft-moran-suit-manifest-04#section-8.2 for
|
* see https://tools.ietf.org/html/draft-ietf-suit-manifest-03#section-7 for
|
||||||
* details
|
* details
|
||||||
*/
|
*/
|
||||||
#define SUIT_VERSION (1)
|
#define SUIT_VERSION (1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief COSE signature OK
|
||||||
|
*/
|
||||||
|
#define SUIT_STATE_COSE_AUTHENTICATED (1 << 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief COSE payload matches SUIT manifest digest
|
||||||
|
*/
|
||||||
|
#define SUIT_STATE_FULLY_AUTHENTICATED (1 << 2)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SUIT error codes
|
* @brief SUIT error codes
|
||||||
*/
|
*/
|
||||||
@ -66,12 +79,13 @@ typedef enum {
|
|||||||
SUIT_OK = 0, /**< Manifest parsed and validated */
|
SUIT_OK = 0, /**< Manifest parsed and validated */
|
||||||
SUIT_ERR_INVALID_MANIFEST = -1, /**< Unexpected CBOR structure detected */
|
SUIT_ERR_INVALID_MANIFEST = -1, /**< Unexpected CBOR structure detected */
|
||||||
SUIT_ERR_UNSUPPORTED = -2, /**< Unsupported SUIT feature detected */
|
SUIT_ERR_UNSUPPORTED = -2, /**< Unsupported SUIT feature detected */
|
||||||
SUIT_ERR_NOT_SUPPORTED = -3, /**< Unsupported manifest features detected */
|
SUIT_ERR_NOT_SUPPORTED = -3, /**< Unsupported features detected */
|
||||||
SUIT_ERR_COND = -4, /**< Conditionals evaluate to false */
|
SUIT_ERR_COND = -4, /**< Conditionals evaluate to false */
|
||||||
SUIT_ERR_SEQUENCE_NUMBER = -5, /**< Sequence number less or equal to
|
SUIT_ERR_SEQUENCE_NUMBER = -5, /**< Sequence number less or equal to
|
||||||
current sequence number */
|
current sequence number */
|
||||||
SUIT_ERR_SIGNATURE = -6, /**< Unable to verify signature */
|
SUIT_ERR_SIGNATURE = -6, /**< Unable to verify signature */
|
||||||
} suit_v4_error_t;
|
SUIT_ERR_DIGEST_MISMATCH = -7, /**< Digest mismatch with COSE and SUIT */
|
||||||
|
} suit_error_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SUIT payload digest algorithms
|
* @brief SUIT payload digest algorithms
|
||||||
@ -84,7 +98,7 @@ typedef enum {
|
|||||||
SUIT_DIGEST_SHA256 = 1, /**< SHA256 */
|
SUIT_DIGEST_SHA256 = 1, /**< SHA256 */
|
||||||
SUIT_DIGEST_SHA384 = 2, /**< SHA384 */
|
SUIT_DIGEST_SHA384 = 2, /**< SHA384 */
|
||||||
SUIT_DIGEST_SHA512 = 3, /**< SHA512 */
|
SUIT_DIGEST_SHA512 = 3, /**< SHA512 */
|
||||||
} suit_v4_digest_t;
|
} suit_digest_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SUIT payload digest types
|
* @brief SUIT payload digest types
|
||||||
@ -97,7 +111,7 @@ typedef enum {
|
|||||||
SUIT_DIGEST_TYPE_INSTALLED = 2, /**< Installed firmware digest */
|
SUIT_DIGEST_TYPE_INSTALLED = 2, /**< Installed firmware digest */
|
||||||
SUIT_DIGEST_TYPE_CIPHERTEXT = 3, /**< Ciphertext digest */
|
SUIT_DIGEST_TYPE_CIPHERTEXT = 3, /**< Ciphertext digest */
|
||||||
SUIT_DIGEST_TYPE_PREIMAGE = 4 /**< Pre-image digest */
|
SUIT_DIGEST_TYPE_PREIMAGE = 4 /**< Pre-image digest */
|
||||||
} suit_v4_digest_type_t;
|
} suit_digest_type_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SUIT component types
|
* @brief SUIT component types
|
||||||
@ -112,36 +126,35 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SUIT v4 component struct
|
* @brief SUIT component struct
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t size; /**< Size */
|
uint32_t size; /**< Size */
|
||||||
nanocbor_value_t identifier; /**< Identifier*/
|
nanocbor_value_t identifier; /**< Identifier */
|
||||||
nanocbor_value_t url; /**< Url */
|
nanocbor_value_t url; /**< Url */
|
||||||
nanocbor_value_t digest; /**< Digest */
|
nanocbor_value_t digest; /**< Digest */
|
||||||
} suit_v4_component_t;
|
} suit_component_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SUIT manifest struct
|
* @brief SUIT manifest struct
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
cose_sign_dec_t verify; /**< COSE signature validation struct */
|
|
||||||
const uint8_t *buf; /**< ptr to the buffer of the manifest */
|
const uint8_t *buf; /**< ptr to the buffer of the manifest */
|
||||||
size_t len; /**< length of the manifest */
|
size_t len; /**< length of the manifest */
|
||||||
|
const uint8_t *cose_payload; /**< ptr to the payload of the COSE sign */
|
||||||
|
size_t cose_payload_len; /**< length of the COSE payload */
|
||||||
uint32_t validated; /**< bitfield of validated policies */
|
uint32_t validated; /**< bitfield of validated policies */
|
||||||
uint32_t state; /**< bitfield holding state information */
|
uint32_t state; /**< bitfield holding state information */
|
||||||
|
|
||||||
/** List of components in the manifest */
|
/** List of components in the manifest */
|
||||||
suit_v4_component_t components[SUIT_V4_COMPONENT_MAX];
|
suit_component_t components[SUIT_COMPONENT_MAX];
|
||||||
unsigned components_len; /**< Current number of components */
|
unsigned components_len; /**< Current number of components */
|
||||||
int32_t component_current; /**< Current component index */
|
uint32_t component_current; /**< Current component index */
|
||||||
riotboot_flashwrite_t *writer; /**< Pointer to the riotboot flash writer */
|
riotboot_flashwrite_t *writer; /**< Pointer to the riotboot flash writer */
|
||||||
/** Manifest validation buffer */
|
/** Manifest validation buffer */
|
||||||
uint8_t validation_buf[SUIT_COSE_BUF_SIZE];
|
uint8_t validation_buf[SUIT_COSE_BUF_SIZE];
|
||||||
cose_key_t *key; /**< Ptr to the public key for validation */
|
|
||||||
char *urlbuf; /**< Buffer containing the manifest url */
|
char *urlbuf; /**< Buffer containing the manifest url */
|
||||||
size_t urlbuf_len; /**< Length of the manifest url */
|
size_t urlbuf_len; /**< Length of the manifest url */
|
||||||
} suit_v4_manifest_t;
|
} suit_manifest_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Bit flags used to determine if SUIT manifest contains components
|
* @brief Bit flags used to determine if SUIT manifest contains components
|
||||||
@ -163,9 +176,9 @@ typedef struct {
|
|||||||
* @param[in] len length of the manifest data in the buffer
|
* @param[in] len length of the manifest data in the buffer
|
||||||
*
|
*
|
||||||
* @return SUIT_OK on parseable manifest
|
* @return SUIT_OK on parseable manifest
|
||||||
* @return negative @ref suit_v4_error_t code on error
|
* @return negative @ref suit_error_t code on error
|
||||||
*/
|
*/
|
||||||
int suit_v4_parse(suit_v4_manifest_t *manifest, const uint8_t *buf, size_t len);
|
int suit_parse(suit_manifest_t *manifest, const uint8_t *buf, size_t len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check a manifest policy
|
* @brief Check a manifest policy
|
||||||
@ -175,31 +188,7 @@ int suit_v4_parse(suit_v4_manifest_t *manifest, const uint8_t *buf, size_t len);
|
|||||||
* @return 0 on valid manifest policy
|
* @return 0 on valid manifest policy
|
||||||
* @return -1 on invalid manifest policy
|
* @return -1 on invalid manifest policy
|
||||||
*/
|
*/
|
||||||
int suit_v4_policy_check(suit_v4_manifest_t *manifest);
|
int suit_policy_check(suit_manifest_t *manifest);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Iterate over a cbor map container
|
|
||||||
*
|
|
||||||
* @param[in] it cbor container iterator
|
|
||||||
* @param[out] key the returned key
|
|
||||||
* @param[out] value the returned value
|
|
||||||
*
|
|
||||||
* @return 0 when the iterator is already at the end of the container
|
|
||||||
* @return the number of returned (key, value) pair, e.g. 1
|
|
||||||
*/
|
|
||||||
int suit_cbor_map_iterate(nanocbor_value_t *it, nanocbor_value_t *key, nanocbor_value_t *value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parser a cbor subsequence
|
|
||||||
*
|
|
||||||
* @param[in] bseq subsequence value
|
|
||||||
* @param[out] it cbor iterator
|
|
||||||
*
|
|
||||||
* @return 0 on success
|
|
||||||
* @return -1 if bseq is not a cbor string
|
|
||||||
* @return CborError code on other cbor parser errors
|
|
||||||
*/
|
|
||||||
int suit_cbor_subparse(nanocbor_value_t *bseq, nanocbor_value_t *it);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper function for writing bytes on flash a specified offset
|
* @brief Helper function for writing bytes on flash a specified offset
|
||||||
@ -220,5 +209,5 @@ int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* SUIT_V4_SUIT_H */
|
#endif /* SUIT_H */
|
||||||
/** @} */
|
/** @} */
|
||||||
201
sys/include/suit/handlers.h
Normal file
201
sys/include/suit/handlers.h
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Koen Zandberg
|
||||||
|
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
* 2019 Inria
|
||||||
|
* 2019 Freie Universität 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @brief SUIT draft-ietf-suit-manifest-03 manifest handlers
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @brief Handler functions for SUIT manifests
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SUIT_HANDLERS_H
|
||||||
|
#define SUIT_HANDLERS_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "suit.h"
|
||||||
|
#include "uuid.h"
|
||||||
|
#include "nanocbor/nanocbor.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name SUIT outer wrapper identifiers
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
#define SUIT_WRAPPER_AUTHENTICATION (2)
|
||||||
|
#define SUIT_WRAPPER_MANIFEST (3)
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name SUIT container identifiers
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
#define SUIT_CONTAINER_VERSION (1)
|
||||||
|
#define SUIT_CONTAINER_SEQ_NO (2)
|
||||||
|
#define SUIT_CONTAINER_COMMON (3)
|
||||||
|
#define SUIT_CONTAINER_DEPS_RESOLUTION (7)
|
||||||
|
#define SUIT_CONTAINER_PAYLOAD_FETCH (8)
|
||||||
|
#define SUIT_CONTAINER_INSTALL (9)
|
||||||
|
#define SUIT_CONTAINER_VALIDATE (10)
|
||||||
|
#define SUIT_CONTAINER_LOAD (11)
|
||||||
|
#define SUIT_CONTAINER_RUN (12)
|
||||||
|
#define SUIT_CONTAINER_TEXT (13)
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name SUIT common section identifiers
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
#define SUIT_COMMON_DEPENDENCIES (1)
|
||||||
|
#define SUIT_COMMON_COMPONENTS (2)
|
||||||
|
#define SUIT_COMMON_DEP_COMPONENTS (3)
|
||||||
|
#define SUIT_COMMON_COMMAND_SEQUENCE (4)
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name SUIT condition identifiers
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
#define SUIT_COND_VENDOR_ID (1)
|
||||||
|
#define SUIT_COND_CLASS_ID (2)
|
||||||
|
#define SUIT_COND_IMAGE_MATCH (3)
|
||||||
|
#define SUIT_COND_USE_BEFORE (4)
|
||||||
|
#define SUIT_COND_COMPONENT_OFFSET (5)
|
||||||
|
#define SUIT_COND_DEVICE_ID (24)
|
||||||
|
#define SUIT_COND_IMAGE_NOT_MATCH (25)
|
||||||
|
#define SUIT_COND_MIN_BATTERY (26)
|
||||||
|
#define SUIT_COND_UPDATE_AUTHZ (27)
|
||||||
|
#define SUIT_COND_VERSION (28)
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name SUIT directive identifiers
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
#define SUIT_DIR_SET_COMPONENT_IDX (12)
|
||||||
|
#define SUIT_DIR_SET_DEPENDENCY_IDX (13)
|
||||||
|
#define SUIT_DIR_ABORT (14)
|
||||||
|
#define SUIT_DIR_TRY_EACH (15)
|
||||||
|
#define SUIT_DIR_PROCESS_DEPS (18)
|
||||||
|
#define SUIT_DIR_SET_PARAM (19)
|
||||||
|
#define SUIT_DIR_OVERRIDE_PARAM (20)
|
||||||
|
#define SUIT_DIR_FETCH (21)
|
||||||
|
#define SUIT_DIR_COPY (22)
|
||||||
|
#define SUIT_DIR_RUN (23)
|
||||||
|
#define SUIT_DIR_WAIT (29)
|
||||||
|
#define SUIT_DIR_RUN_SEQUENCE (30)
|
||||||
|
#define SUIT_DIR_RUN_WITH_ARGS (31)
|
||||||
|
#define SUIT_DIR_SWAP (32)
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief suit handler prototype
|
||||||
|
*
|
||||||
|
* @param manifest SUIT manifest context
|
||||||
|
* @param key SUIT map index of this content
|
||||||
|
* @param it nanocbor_value_t iterator to the content
|
||||||
|
*
|
||||||
|
* @return SUIT_OK on success
|
||||||
|
* @return negative on error
|
||||||
|
*/
|
||||||
|
typedef int (*suit_manifest_handler_t)(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief global handler reference
|
||||||
|
*/
|
||||||
|
extern const suit_manifest_handler_t suit_global_handlers[];
|
||||||
|
extern const size_t suit_global_handlers_len;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SUIT sequence handler reference
|
||||||
|
*/
|
||||||
|
extern const suit_manifest_handler_t suit_command_sequence_handlers[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SUIT sequence handler length
|
||||||
|
*/
|
||||||
|
extern const size_t suit_command_sequence_handlers_len;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SUIT container handlers reference
|
||||||
|
*/
|
||||||
|
extern const suit_manifest_handler_t suit_container_handlers[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief length of the SUIT container handlers
|
||||||
|
*/
|
||||||
|
extern const size_t suit_container_handlers_len;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SUIT common handlers reference
|
||||||
|
*/
|
||||||
|
extern const suit_manifest_handler_t suit_common_handlers[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief length of the SUIT common handlers
|
||||||
|
*/
|
||||||
|
extern const size_t suit_common_handlers_len;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Manifest structure handler function
|
||||||
|
*
|
||||||
|
* Iterates over the supplied nanocbor map or array and calls the manifest
|
||||||
|
* handler function for every key.
|
||||||
|
*
|
||||||
|
* @param manifest SUIT manifest context
|
||||||
|
* @param it Nanocbor map/array element
|
||||||
|
* @param handlers Array of SUIT manifest handlers to use
|
||||||
|
* @param handlers_len Length of the SUIT manifest handlers
|
||||||
|
*
|
||||||
|
* @returns SUIT_OK if all handlers executed successfully
|
||||||
|
* @returns negative on error, see @ref suit_error_t
|
||||||
|
*/
|
||||||
|
int suit_handle_manifest_structure(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *it,
|
||||||
|
const suit_manifest_handler_t *handlers,
|
||||||
|
size_t handlers_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Byte string wrapped manifest structure handler function
|
||||||
|
*
|
||||||
|
* Extracts the nanocbor byte string and Iterates over the CBOR map or array
|
||||||
|
* contained in the bytestring and calls the manifest handler function for
|
||||||
|
* every key.
|
||||||
|
*
|
||||||
|
* @param manifest SUIT manifest context
|
||||||
|
* @param bseq Nanocbor byte string
|
||||||
|
* @param handlers Array of SUIT manifest handlers to use
|
||||||
|
* @param handlers_len Length of the SUIT manifest handlers
|
||||||
|
*
|
||||||
|
* @returns SUIT_OK if all handlers executed successfully
|
||||||
|
* @returns negative on error, see @ref suit_v3_error_t
|
||||||
|
*/
|
||||||
|
int suit_handle_manifest_structure_bstr(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *bseq,
|
||||||
|
const suit_manifest_handler_t *handlers,
|
||||||
|
size_t handlers_len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* SUIT_HANDLERS_H */
|
||||||
|
/** @} */
|
||||||
@ -9,7 +9,7 @@
|
|||||||
* directory for more details.
|
* directory for more details.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @ingroup sys_suit_v4
|
* @ingroup sys_suit
|
||||||
* @brief SUIT policy definitions
|
* @brief SUIT policy definitions
|
||||||
*
|
*
|
||||||
* @{
|
* @{
|
||||||
@ -20,8 +20,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SUIT_V4_POLICY_H
|
#ifndef SUIT_POLICY_H
|
||||||
#define SUIT_V4_POLICY_H
|
#define SUIT_POLICY_H
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -48,11 +48,12 @@ extern "C" {
|
|||||||
* @brief SUIT default policy
|
* @brief SUIT default policy
|
||||||
*/
|
*/
|
||||||
#define SUIT_DEFAULT_POLICY \
|
#define SUIT_DEFAULT_POLICY \
|
||||||
(SUIT_VALIDATED_VERSION | SUIT_VALIDATED_SEQ_NR | SUIT_VALIDATED_VENDOR | SUIT_VALIDATED_CLASS)
|
(SUIT_VALIDATED_VERSION | SUIT_VALIDATED_SEQ_NR | SUIT_VALIDATED_VENDOR | \
|
||||||
|
SUIT_VALIDATED_CLASS)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* SUIT_V4_POLICY_H */
|
#endif /* SUIT_POLICY_H */
|
||||||
/** @} */
|
/** @} */
|
||||||
@ -9,11 +9,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @defgroup sys_suit SUIT secure firmware updates
|
* @ingroup sys_suit
|
||||||
* @ingroup sys
|
* @defgroup sys_suit_transport_coap SUIT firmware CoAP transport
|
||||||
* @brief SUIT secure firmware updates
|
* @brief SUIT secure firmware updates over CoAP
|
||||||
*
|
|
||||||
* @experimental
|
|
||||||
*
|
*
|
||||||
* @{
|
* @{
|
||||||
*
|
*
|
||||||
@ -24,8 +22,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SUIT_COAP_H
|
#ifndef SUIT_TRANSPORT_COAP_H
|
||||||
#define SUIT_COAP_H
|
#define SUIT_TRANSPORT_COAP_H
|
||||||
|
|
||||||
#include "net/nanocoap.h"
|
#include "net/nanocoap.h"
|
||||||
|
|
||||||
@ -157,5 +155,5 @@ void suit_coap_trigger(const uint8_t *url, size_t len);
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* SUIT_COAP_H */
|
#endif /* SUIT_TRANSPORT_COAP_H */
|
||||||
/** @} */
|
/** @} */
|
||||||
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 Koen Zandberg
|
|
||||||
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
|
|
||||||
* 2019 Inria
|
|
||||||
* 2019 Freie Universität 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.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @ingroup sys_suit_v4
|
|
||||||
* @brief SUIT v4 manifest handlers
|
|
||||||
*
|
|
||||||
* @experimental
|
|
||||||
*
|
|
||||||
* @{
|
|
||||||
*
|
|
||||||
* @brief Handler functions for SUIT manifests
|
|
||||||
* @author Koen Zandberg <koen@bergzand.net>
|
|
||||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SUIT_V4_HANDLERS_H
|
|
||||||
#define SUIT_V4_HANDLERS_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "suit/v4/suit.h"
|
|
||||||
#include "uuid.h"
|
|
||||||
#include "nanocbor/nanocbor.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief suit handler prototype
|
|
||||||
*
|
|
||||||
* @param manifest SUIT v4 manifest context
|
|
||||||
* @param it nanocbor_value_t iterator to the content the handler must handle
|
|
||||||
*
|
|
||||||
* @return 1 on success
|
|
||||||
* @return negative on error
|
|
||||||
*/
|
|
||||||
typedef int (*suit_manifest_handler_t)(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get suit manifest handler for given integer key
|
|
||||||
*
|
|
||||||
* @param[in] key: integer key
|
|
||||||
*
|
|
||||||
* @return ptr to handler function
|
|
||||||
* @return NULL (if handler unavailable or key out of range)
|
|
||||||
*/
|
|
||||||
suit_manifest_handler_t suit_manifest_get_manifest_handler(int key);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* SUIT_V4_HANDLERS_H */
|
|
||||||
/** @} */
|
|
||||||
@ -22,7 +22,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include "suit/coap.h"
|
#include "suit/transport/coap.h"
|
||||||
|
|
||||||
|
|
||||||
int _suit_handler(int argc, char **argv)
|
int _suit_handler(int argc, char **argv)
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
SUBMODULES := 1
|
ifneq (,$(filter suit_transport_%,$(USEMODULE)))
|
||||||
|
DIRS += transport
|
||||||
# don't complain about missing submodule .c file.
|
endif
|
||||||
# necessary to not fail for suit_v*_*.
|
|
||||||
SUBMODULES_NOFORCE := 1
|
|
||||||
|
|
||||||
DIRS += v4
|
|
||||||
|
|
||||||
include $(RIOTBASE)/Makefile.base
|
include $(RIOTBASE)/Makefile.base
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
* @{
|
* @{
|
||||||
*
|
*
|
||||||
* @file
|
* @file
|
||||||
* @brief SUIT conditions
|
* @brief SUIT condition initializers
|
||||||
*
|
*
|
||||||
* @author Koen Zandberg <koen@bergzand.net>
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
|||||||
97
sys/suit/handlers.c
Normal file
97
sys/suit/handlers.c
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Koen Zandberg
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief SUIT content handler helper functions
|
||||||
|
*
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <nanocbor/nanocbor.h>
|
||||||
|
|
||||||
|
#include "suit/handlers.h"
|
||||||
|
#include "suit.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
static suit_manifest_handler_t _get_handler(int key,
|
||||||
|
const suit_manifest_handler_t *map,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
if (key < 0 || (size_t)key >= len) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return map[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
int suit_handle_manifest_structure(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *it,
|
||||||
|
const suit_manifest_handler_t *handlers,
|
||||||
|
size_t handlers_len)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("Handling command sequence\n");
|
||||||
|
nanocbor_value_t container;
|
||||||
|
|
||||||
|
if ((nanocbor_enter_array(it, &container) < 0) &&
|
||||||
|
(nanocbor_enter_map(it, &container) < 0)) {
|
||||||
|
LOG_DEBUG("Neither array nor map: %d\n", nanocbor_get_type(it));
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!nanocbor_at_end(&container)) {
|
||||||
|
int32_t key;
|
||||||
|
if (nanocbor_get_int32(&container, &key) < 0) {
|
||||||
|
LOG_DEBUG("No key found: %d\n", nanocbor_get_type(&container));
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
nanocbor_value_t value = container;
|
||||||
|
LOG_DEBUG("Executing handler with key %" PRIi32 "\n", key);
|
||||||
|
suit_manifest_handler_t handler = _get_handler(key, handlers,
|
||||||
|
handlers_len);
|
||||||
|
if (!handler) {
|
||||||
|
return SUIT_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
int res = handler(manifest, key, &value);
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_DEBUG("Sequence handler error\n");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
nanocbor_skip(&container);
|
||||||
|
}
|
||||||
|
nanocbor_leave_container(it, &container);
|
||||||
|
LOG_DEBUG("Leaving sequence handler\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int suit_handle_manifest_structure_bstr(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *bseq,
|
||||||
|
const suit_manifest_handler_t *handlers,
|
||||||
|
size_t handlers_len)
|
||||||
|
{
|
||||||
|
const uint8_t *buf;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
LOG_DEBUG("Handling command sequence starting with CBOR type %d\n",
|
||||||
|
nanocbor_get_type(bseq));
|
||||||
|
if (nanocbor_get_bstr(bseq, &buf, &len) < 0) {
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
nanocbor_value_t it;
|
||||||
|
nanocbor_decoder_init(&it, buf, len);
|
||||||
|
return suit_handle_manifest_structure(manifest, &it, handlers,
|
||||||
|
handlers_len);
|
||||||
|
}
|
||||||
341
sys/suit/handlers_command_seq.c
Normal file
341
sys/suit/handlers_command_seq.c
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Koen Zandberg
|
||||||
|
* 2020 Inria
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief SUIT Handlers for the command sequences in the common section of
|
||||||
|
* a SUIT manifest.
|
||||||
|
*
|
||||||
|
* This file contains the functions to handle command sequences from a SUIT
|
||||||
|
* manifest. This includes both directives and conditions.
|
||||||
|
*
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <nanocbor/nanocbor.h>
|
||||||
|
|
||||||
|
#include "kernel_defines.h"
|
||||||
|
#include "suit/conditions.h"
|
||||||
|
#include "suit/handlers.h"
|
||||||
|
#include "suit/policy.h"
|
||||||
|
#include "suit.h"
|
||||||
|
#include "riotboot/hdr.h"
|
||||||
|
#include "riotboot/slot.h"
|
||||||
|
|
||||||
|
#ifdef MODULE_SUIT_TRANSPORT_COAP
|
||||||
|
#include "suit/transport/coap.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
static int _validate_uuid(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *it,
|
||||||
|
uuid_t *uuid)
|
||||||
|
{
|
||||||
|
(void)manifest;
|
||||||
|
const uint8_t *uuid_manifest_ptr;
|
||||||
|
size_t len = sizeof(uuid_t);
|
||||||
|
char uuid_str[UUID_STR_LEN + 1];
|
||||||
|
char uuid_str2[UUID_STR_LEN + 1];
|
||||||
|
if (nanocbor_get_bstr(it, &uuid_manifest_ptr, &len) < 0) {
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid_to_string((uuid_t *)uuid_manifest_ptr, uuid_str);
|
||||||
|
uuid_to_string(uuid, uuid_str2);
|
||||||
|
LOG_INFO("Comparing %s to %s from manifest\n", uuid_str2, uuid_str);
|
||||||
|
return uuid_equal(uuid, (uuid_t *)uuid_manifest_ptr)
|
||||||
|
? SUIT_OK
|
||||||
|
: SUIT_ERR_COND;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _cond_vendor_handler(suit_manifest_t *manifest,
|
||||||
|
int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
LOG_INFO("validating vendor ID\n");
|
||||||
|
int rc = _validate_uuid(manifest, it, suit_get_vendor_id());
|
||||||
|
if (rc == SUIT_OK) {
|
||||||
|
LOG_INFO("validating vendor ID: OK\n");
|
||||||
|
manifest->validated |= SUIT_VALIDATED_VENDOR;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _cond_class_handler(suit_manifest_t *manifest,
|
||||||
|
int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
LOG_INFO("validating class id\n");
|
||||||
|
int rc = _validate_uuid(manifest, it, suit_get_class_id());
|
||||||
|
if (rc == SUIT_OK) {
|
||||||
|
LOG_INFO("validating class id: OK\n");
|
||||||
|
manifest->validated |= SUIT_VALIDATED_CLASS;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _cond_comp_offset(suit_manifest_t *manifest,
|
||||||
|
int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)manifest;
|
||||||
|
(void)key;
|
||||||
|
uint32_t offset;
|
||||||
|
|
||||||
|
int rc = nanocbor_get_uint32(it, &offset);
|
||||||
|
if (rc < 0) {
|
||||||
|
LOG_WARNING("_cond_comp_offset(): expected int, got rc=%i type=%i\n",
|
||||||
|
rc, nanocbor_get_type(it));
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
uint32_t other_offset = (uint32_t)riotboot_slot_offset(
|
||||||
|
riotboot_slot_other());
|
||||||
|
|
||||||
|
LOG_INFO("Comparing manifest offset %u with other slot offset %u\n",
|
||||||
|
(unsigned)offset, (unsigned)other_offset);
|
||||||
|
return other_offset == offset ? SUIT_OK : SUIT_ERR_COND;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dtv_set_comp_idx(suit_manifest_t *manifest,
|
||||||
|
int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
if (nanocbor_get_type(it) == NANOCBOR_TYPE_FLOAT) {
|
||||||
|
LOG_DEBUG("_dtv_set_comp_idx() ignoring boolean and floats\n)");
|
||||||
|
nanocbor_skip(it);
|
||||||
|
}
|
||||||
|
else if (nanocbor_get_uint32(it, &manifest->component_current) < 0) {
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
if (manifest->component_current >= SUIT_COMPONENT_MAX) {
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
LOG_DEBUG("Setting component index to %d\n",
|
||||||
|
(int)manifest->component_current);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dtv_run_seq_cond(suit_manifest_t *manifest,
|
||||||
|
int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
LOG_DEBUG("Starting conditional sequence handler\n");
|
||||||
|
return suit_handle_manifest_structure_bstr(manifest, it,
|
||||||
|
suit_command_sequence_handlers,
|
||||||
|
suit_command_sequence_handlers_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dtv_try_each(suit_manifest_t *manifest,
|
||||||
|
int key, nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
LOG_DEBUG("Starting suit-directive-try-each handler\n");
|
||||||
|
nanocbor_value_t container;
|
||||||
|
|
||||||
|
if ((nanocbor_enter_array(it, &container) < 0) &&
|
||||||
|
(nanocbor_enter_map(it, &container) < 0)) {
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = SUIT_ERR_COND;
|
||||||
|
while (!nanocbor_at_end(&container)) {
|
||||||
|
nanocbor_value_t _container = container;
|
||||||
|
/* `_container` should be CBOR _bstr wrapped according to the spec, but
|
||||||
|
* it is not */
|
||||||
|
res = suit_handle_manifest_structure(manifest, &_container,
|
||||||
|
suit_command_sequence_handlers,
|
||||||
|
suit_command_sequence_handlers_len);
|
||||||
|
|
||||||
|
nanocbor_skip(&container);
|
||||||
|
|
||||||
|
if (res != SUIT_ERR_COND) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _param_get_uri_list(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("got url list\n");
|
||||||
|
manifest->components[manifest->component_current].url = *it;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
static int _param_get_digest(suit_manifest_t *manifest, nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("got digest\n");
|
||||||
|
manifest->components[manifest->component_current].digest = *it;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _param_get_img_size(suit_manifest_t *manifest,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
int res = nanocbor_get_uint32(it, &manifest->components[0].size);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_DEBUG("error getting image size\n");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dtv_set_param(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
/* `it` points to the entry of the map containing the type and value */
|
||||||
|
nanocbor_value_t map;
|
||||||
|
|
||||||
|
nanocbor_enter_map(it, &map);
|
||||||
|
|
||||||
|
while (!nanocbor_at_end(&map)) {
|
||||||
|
/* map points to the key of the param */
|
||||||
|
int32_t param_key;
|
||||||
|
nanocbor_get_int32(&map, ¶m_key);
|
||||||
|
LOG_DEBUG("Current component index: %" PRIi32 "\n",
|
||||||
|
manifest->component_current);
|
||||||
|
LOG_DEBUG("param_key=%" PRIi32 "\n", param_key);
|
||||||
|
int res;
|
||||||
|
switch (param_key) {
|
||||||
|
case 6: /* SUIT URI LIST */
|
||||||
|
res = _param_get_uri_list(manifest, &map);
|
||||||
|
break;
|
||||||
|
case 11: /* SUIT DIGEST */
|
||||||
|
res = _param_get_digest(manifest, &map);
|
||||||
|
break;
|
||||||
|
case 12: /* SUIT IMAGE SIZE */
|
||||||
|
res = _param_get_img_size(manifest, &map);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_DEBUG("Unsupported parameter %" PRIi32 "\n", param_key);
|
||||||
|
res = SUIT_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
nanocbor_skip(&map);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SUIT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dtv_fetch(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *_it)
|
||||||
|
{
|
||||||
|
(void)key; (void)_it;
|
||||||
|
LOG_DEBUG("_dtv_fetch() key=%i\n", key);
|
||||||
|
|
||||||
|
const uint8_t *url;
|
||||||
|
size_t url_len;
|
||||||
|
|
||||||
|
int err = nanocbor_get_tstr(&manifest->components[0].url, &url, &url_len);
|
||||||
|
if (err < 0) {
|
||||||
|
LOG_DEBUG("URL parsing failed\n)");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
memcpy(manifest->urlbuf, url, url_len);
|
||||||
|
manifest->urlbuf[url_len] = '\0';
|
||||||
|
|
||||||
|
LOG_DEBUG("_dtv_fetch() fetching \"%s\" (url_len=%u)\n", manifest->urlbuf,
|
||||||
|
(unsigned)url_len);
|
||||||
|
|
||||||
|
int target_slot = riotboot_slot_other();
|
||||||
|
riotboot_flashwrite_init(manifest->writer, target_slot);
|
||||||
|
|
||||||
|
int res = -1;
|
||||||
|
|
||||||
|
if (0) {}
|
||||||
|
#ifdef MODULE_SUIT_TRANSPORT_COAP
|
||||||
|
else if (strncmp(manifest->urlbuf, "coap://", 7) == 0) {
|
||||||
|
res = suit_coap_get_blockwise_url(manifest->urlbuf, COAP_BLOCKSIZE_64,
|
||||||
|
suit_flashwrite_helper,
|
||||||
|
manifest);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef MODULE_SUIT_TRANSPORT_MOCK
|
||||||
|
else if (strncmp(manifest->urlbuf, "test://", 7) == 0) {
|
||||||
|
res = SUIT_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else {
|
||||||
|
LOG_WARNING("suit: unsupported URL scheme!\n)");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
LOG_INFO("image download failed\n)");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest->state |= SUIT_MANIFEST_HAVE_IMAGE;
|
||||||
|
|
||||||
|
LOG_DEBUG("Update OK\n");
|
||||||
|
return SUIT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dtv_verify_image_match(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *_it)
|
||||||
|
{
|
||||||
|
(void)key; (void)_it;
|
||||||
|
LOG_DEBUG("dtv_image_match\n");
|
||||||
|
const uint8_t *digest;
|
||||||
|
size_t digest_len;
|
||||||
|
int target_slot = riotboot_slot_other();
|
||||||
|
|
||||||
|
LOG_INFO("Verifying image digest\n");
|
||||||
|
nanocbor_value_t _v = manifest->components[0].digest;
|
||||||
|
int res = nanocbor_get_subcbor(&_v, &digest, &digest_len);
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_DEBUG("Unable to parse digest structure\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "digest" points to a 36 byte string that includes the digest type.
|
||||||
|
* riotboot_flashwrite_verify_sha256() is only interested in the 32b digest,
|
||||||
|
* so shift the pointer accordingly.
|
||||||
|
*/
|
||||||
|
res = riotboot_flashwrite_verify_sha256(digest + 4,
|
||||||
|
manifest->components[0].size,
|
||||||
|
target_slot);
|
||||||
|
if (res != 0) {
|
||||||
|
return SUIT_ERR_COND;
|
||||||
|
}
|
||||||
|
return SUIT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* begin{code-style-ignore} */
|
||||||
|
const suit_manifest_handler_t suit_command_sequence_handlers[] = {
|
||||||
|
[SUIT_COND_VENDOR_ID] = _cond_vendor_handler,
|
||||||
|
[SUIT_COND_CLASS_ID] = _cond_class_handler,
|
||||||
|
[SUIT_COND_IMAGE_MATCH] = _dtv_verify_image_match,
|
||||||
|
[SUIT_COND_COMPONENT_OFFSET] = _cond_comp_offset,
|
||||||
|
[SUIT_DIR_SET_COMPONENT_IDX] = _dtv_set_comp_idx,
|
||||||
|
[SUIT_DIR_TRY_EACH] = _dtv_try_each,
|
||||||
|
[SUIT_DIR_SET_PARAM] = _dtv_set_param,
|
||||||
|
[SUIT_DIR_OVERRIDE_PARAM] = _dtv_set_param,
|
||||||
|
[SUIT_DIR_FETCH] = _dtv_fetch,
|
||||||
|
[SUIT_DIR_RUN_SEQUENCE] = _dtv_run_seq_cond,
|
||||||
|
};
|
||||||
|
/* end{code-style-ignore} */
|
||||||
|
|
||||||
|
const size_t suit_command_sequence_handlers_len = ARRAY_SIZE(suit_command_sequence_handlers);
|
||||||
110
sys/suit/handlers_common.c
Normal file
110
sys/suit/handlers_common.c
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Koen Zandberg
|
||||||
|
* 2020 Inria
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief SUIT handler implementations for the manifest Common sections
|
||||||
|
*
|
||||||
|
* This file contains functions to handle the common info sections of a manifest.
|
||||||
|
* This includes components, dependencies and command sequences.
|
||||||
|
*
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <nanocbor/nanocbor.h>
|
||||||
|
|
||||||
|
#include "kernel_defines.h"
|
||||||
|
#include "suit/handlers.h"
|
||||||
|
#include "suit.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
static int _component_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)manifest;
|
||||||
|
(void)key;
|
||||||
|
const uint8_t *subcbor;
|
||||||
|
size_t sub_size;
|
||||||
|
nanocbor_value_t _it;
|
||||||
|
nanocbor_get_bstr(it, &subcbor, &sub_size);
|
||||||
|
nanocbor_decoder_init(&_it, subcbor, sub_size);
|
||||||
|
|
||||||
|
/* This is a list of lists, something like:
|
||||||
|
* [
|
||||||
|
* [ "sda" "firmwareA" ],
|
||||||
|
* [ "sda" "firmwareB" ]
|
||||||
|
* ]
|
||||||
|
* */
|
||||||
|
nanocbor_value_t arr;
|
||||||
|
if (nanocbor_enter_array(&_it, &arr) < 0) {
|
||||||
|
LOG_DEBUG("components field not an array %d\n", nanocbor_get_type(it));
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
unsigned n = 0;
|
||||||
|
while (!nanocbor_at_end(&arr)) {
|
||||||
|
nanocbor_value_t comp;
|
||||||
|
if (nanocbor_enter_array(&arr, &comp) < 0) {
|
||||||
|
LOG_DEBUG("component elements field not an array %d\n",
|
||||||
|
nanocbor_get_type(it));
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
while (!nanocbor_at_end(&comp)) {
|
||||||
|
const uint8_t *identifier;
|
||||||
|
size_t id_len;
|
||||||
|
if (nanocbor_get_bstr(&comp, &identifier, &id_len) < 0) {
|
||||||
|
LOG_DEBUG("Component name not a byte string\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nanocbor_leave_container(&arr, &comp);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
if (n > 1) {
|
||||||
|
LOG_INFO("More than 1 component found, exiting\n");
|
||||||
|
return SUIT_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
manifest->state |= SUIT_MANIFEST_HAVE_COMPONENTS;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _dependencies_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)manifest;
|
||||||
|
(void)key;
|
||||||
|
(void)it;
|
||||||
|
/* No dependency support */
|
||||||
|
return SUIT_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _common_sequence_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
LOG_DEBUG("Starting conditional sequence handler\n");
|
||||||
|
return suit_handle_manifest_structure_bstr(manifest, it,
|
||||||
|
suit_command_sequence_handlers,
|
||||||
|
suit_command_sequence_handlers_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* begin{code-style-ignore} */
|
||||||
|
const suit_manifest_handler_t suit_common_handlers[] = {
|
||||||
|
[SUIT_COMMON_DEPENDENCIES] = _dependencies_handler,
|
||||||
|
[SUIT_COMMON_COMPONENTS] = _component_handler,
|
||||||
|
[SUIT_COMMON_COMMAND_SEQUENCE] = _common_sequence_handler,
|
||||||
|
};
|
||||||
|
/* end{code-style-ignore} */
|
||||||
|
|
||||||
|
const size_t suit_common_handlers_len = ARRAY_SIZE(suit_common_handlers);
|
||||||
151
sys/suit/handlers_container.c
Normal file
151
sys/suit/handlers_container.c
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Koen Zandberg
|
||||||
|
* 2020 Inria
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief SUIT handlers for the SUIT outer wrapper
|
||||||
|
*
|
||||||
|
* This file contains the handlers for the content of the SUIT outer wrapper.
|
||||||
|
* This includes the authentication wrapper and the manifest itself.
|
||||||
|
*
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cose/sign.h>
|
||||||
|
#include <nanocbor/nanocbor.h>
|
||||||
|
|
||||||
|
#include "hashes/sha256.h"
|
||||||
|
#include "kernel_defines.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "public_key.h"
|
||||||
|
#include "suit/conditions.h"
|
||||||
|
#include "suit/handlers.h"
|
||||||
|
#include "suit.h"
|
||||||
|
|
||||||
|
static int _auth_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
cose_sign_dec_t verify;
|
||||||
|
const uint8_t *cose_buf;
|
||||||
|
const uint8_t *cose_container;
|
||||||
|
size_t container_len;
|
||||||
|
size_t cose_len = 0;
|
||||||
|
/* It is a list of cose signatures */
|
||||||
|
int res = nanocbor_get_bstr(it, &cose_container, &container_len);
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_INFO("Unable to get COSE signature\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
nanocbor_value_t _cont, arr;
|
||||||
|
nanocbor_decoder_init(&_cont, cose_container, container_len);
|
||||||
|
|
||||||
|
int rc = nanocbor_enter_array(&_cont, &arr);
|
||||||
|
if (rc < 0) {
|
||||||
|
LOG_INFO("Unable to enter COSE signatures\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t tag;
|
||||||
|
nanocbor_get_tag(&arr, &tag);
|
||||||
|
arr.remaining++;
|
||||||
|
res = nanocbor_get_subcbor(&arr, &cose_buf, &cose_len);
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_INFO("Unable to get subcbor: %d\n", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = cose_sign_decode(&verify, cose_buf, cose_len);
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_INFO("Unable to parse COSE signature\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate over signatures, should only be a single signature */
|
||||||
|
cose_signature_dec_t signature;
|
||||||
|
|
||||||
|
cose_sign_signature_iter_init(&signature);
|
||||||
|
if (!cose_sign_signature_iter(&verify, &signature)) {
|
||||||
|
LOG_INFO("Unable to get signature iteration\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize key from hardcoded public key */
|
||||||
|
cose_key_t pkey;
|
||||||
|
cose_key_init(&pkey);
|
||||||
|
cose_key_set_keys(&pkey, COSE_EC_CURVE_ED25519, COSE_ALGO_EDDSA,
|
||||||
|
(uint8_t *)public_key, NULL, NULL);
|
||||||
|
|
||||||
|
LOG_INFO("suit: verifying manifest signature\n");
|
||||||
|
int verification = cose_sign_verify(&verify, &signature,
|
||||||
|
&pkey, manifest->validation_buf,
|
||||||
|
SUIT_COSE_BUF_SIZE);
|
||||||
|
if (verification != 0) {
|
||||||
|
LOG_INFO("Unable to validate signature: %d\n", verification);
|
||||||
|
return SUIT_ERR_SIGNATURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest->cose_payload = verify.payload;
|
||||||
|
manifest->cose_payload_len = verify.payload_len;
|
||||||
|
manifest->state |= SUIT_STATE_COSE_AUTHENTICATED;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _manifest_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
const uint8_t *manifest_buf;
|
||||||
|
size_t manifest_len;
|
||||||
|
|
||||||
|
if (!(manifest->state & SUIT_STATE_COSE_AUTHENTICATED)) {
|
||||||
|
return SUIT_ERR_SIGNATURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nanocbor_value_t cbor_buf = *it;
|
||||||
|
|
||||||
|
nanocbor_get_subcbor(&cbor_buf, &manifest_buf, &manifest_len);
|
||||||
|
|
||||||
|
uint8_t digest_struct[4 + SHA256_DIGEST_LENGTH] =
|
||||||
|
/* CBOR array of length 2, sha256 digest and a bytestring of SHA256
|
||||||
|
* length
|
||||||
|
*/
|
||||||
|
{ 0x82, 0x02, 0x58, SHA256_DIGEST_LENGTH };
|
||||||
|
sha256(manifest_buf, manifest_len, digest_struct + 4);
|
||||||
|
|
||||||
|
/* The COSE payload and the sha256 of the manifest itself is public info and
|
||||||
|
* verification does not depend on secret info. No need for cryptographic
|
||||||
|
* memcmp here */
|
||||||
|
if (memcmp(digest_struct, manifest->cose_payload,
|
||||||
|
sizeof(digest_struct)) != 0) {
|
||||||
|
LOG_ERROR("SUIT manifest digest and COSE digest mismatch\n");
|
||||||
|
return SUIT_ERR_DIGEST_MISMATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest->state |= SUIT_STATE_FULLY_AUTHENTICATED;
|
||||||
|
|
||||||
|
LOG_DEBUG("Starting global sequence handler\n");
|
||||||
|
return suit_handle_manifest_structure_bstr(manifest, it,
|
||||||
|
suit_global_handlers,
|
||||||
|
suit_global_handlers_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* begin{code-style-ignore} */
|
||||||
|
const suit_manifest_handler_t suit_container_handlers[] = {
|
||||||
|
[SUIT_WRAPPER_AUTHENTICATION] = _auth_handler,
|
||||||
|
[SUIT_WRAPPER_MANIFEST] = _manifest_handler,
|
||||||
|
};
|
||||||
|
/* end{code-style-ignore} */
|
||||||
|
|
||||||
|
const size_t suit_container_handlers_len = ARRAY_SIZE(suit_container_handlers);
|
||||||
107
sys/suit/handlers_global.c
Normal file
107
sys/suit/handlers_global.c
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Koen Zandberg
|
||||||
|
* 2020 Inria
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief SUIT handlers for the global SUIT manifest content.
|
||||||
|
*
|
||||||
|
* This file includes the implementation of the SUIT manifest content.
|
||||||
|
*
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <nanocbor/nanocbor.h>
|
||||||
|
|
||||||
|
#include "kernel_defines.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "suit/conditions.h"
|
||||||
|
#include "suit/handlers.h"
|
||||||
|
#include "suit/policy.h"
|
||||||
|
#include "suit.h"
|
||||||
|
|
||||||
|
extern int _common_sequence_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it);
|
||||||
|
|
||||||
|
static int _version_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)manifest;
|
||||||
|
(void)key;
|
||||||
|
/* Validate manifest version */
|
||||||
|
int32_t version = -1;
|
||||||
|
if (nanocbor_get_int32(it, &version) >= 0) {
|
||||||
|
if (version == SUIT_VERSION) {
|
||||||
|
manifest->validated |= SUIT_VALIDATED_VERSION;
|
||||||
|
LOG_INFO("suit: validated manifest version\n)");
|
||||||
|
return SUIT_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SUIT_ERR_SEQUENCE_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _seq_no_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
|
||||||
|
int32_t seq_nr;
|
||||||
|
|
||||||
|
if (nanocbor_get_int32(it, &seq_nr) < 0) {
|
||||||
|
LOG_INFO("Unable to get sequence number\n");
|
||||||
|
return SUIT_ERR_INVALID_MANIFEST;
|
||||||
|
}
|
||||||
|
const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_current());
|
||||||
|
if (seq_nr <= (int32_t)hdr->version) {
|
||||||
|
LOG_INFO("%" PRId32 " <= %" PRId32 "\n", seq_nr, hdr->version);
|
||||||
|
LOG_INFO("seq_nr <= running image\n)");
|
||||||
|
return SUIT_ERR_SEQUENCE_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr = riotboot_slot_get_hdr(riotboot_slot_other());
|
||||||
|
if (riotboot_hdr_validate(hdr) == 0) {
|
||||||
|
if (seq_nr <= (int32_t)hdr->version) {
|
||||||
|
LOG_INFO("%" PRIu32 " <= %" PRIu32 "\n", seq_nr, hdr->version);
|
||||||
|
LOG_INFO("seq_nr <= other image\n)");
|
||||||
|
return SUIT_ERR_SEQUENCE_NUMBER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_INFO("suit: validated sequence number\n)");
|
||||||
|
manifest->validated |= SUIT_VALIDATED_SEQ_NR;
|
||||||
|
return SUIT_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _common_handler(suit_manifest_t *manifest, int key,
|
||||||
|
nanocbor_value_t *it)
|
||||||
|
{
|
||||||
|
(void)key;
|
||||||
|
LOG_DEBUG("Starting common section handler\n");
|
||||||
|
return suit_handle_manifest_structure_bstr(manifest, it,
|
||||||
|
suit_common_handlers,
|
||||||
|
suit_common_handlers_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* begin{code-style-ignore} */
|
||||||
|
const suit_manifest_handler_t suit_global_handlers[] = {
|
||||||
|
[ 0] = NULL,
|
||||||
|
[SUIT_CONTAINER_VERSION] = _version_handler,
|
||||||
|
[SUIT_CONTAINER_SEQ_NO] = _seq_no_handler,
|
||||||
|
[SUIT_CONTAINER_COMMON] = _common_handler,
|
||||||
|
/* Install and validate both consist of a command sequence */
|
||||||
|
[SUIT_CONTAINER_INSTALL] = _common_sequence_handler,
|
||||||
|
[SUIT_CONTAINER_VALIDATE] = _common_sequence_handler,
|
||||||
|
};
|
||||||
|
/* end{code-style-ignore} */
|
||||||
|
|
||||||
|
const size_t suit_global_handlers_len = ARRAY_SIZE(suit_global_handlers);
|
||||||
@ -8,23 +8,22 @@
|
|||||||
* directory for more details.
|
* directory for more details.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @ingroup sys_suit_v4
|
* @ingroup sys_suit
|
||||||
* @{
|
* @{
|
||||||
*
|
*
|
||||||
* @file
|
* @file
|
||||||
* @brief SUIT v4 policy checking code
|
* @brief SUIT update policy checking code
|
||||||
*
|
*
|
||||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
*
|
*
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "suit/v4/suit.h"
|
|
||||||
#include "suit/v4/policy.h"
|
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "suit/policy.h"
|
||||||
|
#include "suit.h"
|
||||||
|
|
||||||
int suit_v4_policy_check(suit_v4_manifest_t *manifest)
|
int suit_policy_check(suit_manifest_t *manifest)
|
||||||
{
|
{
|
||||||
if (SUIT_DEFAULT_POLICY & ~(manifest->validated)) {
|
if (SUIT_DEFAULT_POLICY & ~(manifest->validated)) {
|
||||||
LOG_INFO("SUIT policy check failed!\n");
|
LOG_INFO("SUIT policy check failed!\n");
|
||||||
49
sys/suit/suit.c
Normal file
49
sys/suit/suit.c
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Freie Universität Berlin
|
||||||
|
* Copyright (C) 2019 Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
* 2020 Inria
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ingroup sys_suit
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief SUIT secure OTA firmware upgrade implementation for
|
||||||
|
* CBOR based manifests
|
||||||
|
*
|
||||||
|
* @author Koen Zandberg <koen@bergzand.net>
|
||||||
|
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <nanocbor/nanocbor.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "suit/handlers.h"
|
||||||
|
#include "suit/policy.h"
|
||||||
|
#include "suit.h"
|
||||||
|
|
||||||
|
#define ENABLE_DEBUG (0)
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
int suit_parse(suit_manifest_t *manifest, const uint8_t *buf,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
nanocbor_value_t it;
|
||||||
|
|
||||||
|
manifest->buf = buf;
|
||||||
|
manifest->len = len;
|
||||||
|
nanocbor_decoder_init(&it, buf, len);
|
||||||
|
LOG_DEBUG("Starting container sequence handler\n");
|
||||||
|
return suit_handle_manifest_structure(manifest, &it,
|
||||||
|
suit_container_handlers,
|
||||||
|
suit_container_handlers_len);
|
||||||
|
}
|
||||||
5
sys/suit/transport/Makefile
Normal file
5
sys/suit/transport/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
MODULE := suit_transport
|
||||||
|
SUBMODULES := 1
|
||||||
|
BASE_MODULE := suit_transport
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.base
|
||||||
@ -32,7 +32,7 @@
|
|||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "periph/pm.h"
|
#include "periph/pm.h"
|
||||||
|
|
||||||
#include "suit/coap.h"
|
#include "suit/transport/coap.h"
|
||||||
#include "net/sock/util.h"
|
#include "net/sock/util.h"
|
||||||
|
|
||||||
#ifdef MODULE_RIOTBOOT_SLOT
|
#ifdef MODULE_RIOTBOOT_SLOT
|
||||||
@ -40,8 +40,8 @@
|
|||||||
#include "riotboot/flashwrite.h"
|
#include "riotboot/flashwrite.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MODULE_SUIT_V4
|
#ifdef MODULE_SUIT
|
||||||
#include "suit/v4/suit.h"
|
#include "suit.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(MODULE_PROGRESS_BAR)
|
#if defined(MODULE_PROGRESS_BAR)
|
||||||
@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
#ifndef SUIT_COAP_STACKSIZE
|
#ifndef SUIT_COAP_STACKSIZE
|
||||||
/* allocate stack needed to keep a page buffer and do manifest validation */
|
/* allocate stack needed to keep a page buffer and do manifest validation */
|
||||||
#define SUIT_COAP_STACKSIZE (3*THREAD_STACKSIZE_LARGE + FLASHPAGE_SIZE)
|
#define SUIT_COAP_STACKSIZE (3 * THREAD_STACKSIZE_LARGE + FLASHPAGE_SIZE)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SUIT_COAP_PRIO
|
#ifndef SUIT_COAP_PRIO
|
||||||
@ -74,8 +74,9 @@ static char _stack[SUIT_COAP_STACKSIZE];
|
|||||||
static char _url[SUIT_URL_MAX];
|
static char _url[SUIT_URL_MAX];
|
||||||
static uint8_t _manifest_buf[SUIT_MANIFEST_BUFSIZE];
|
static uint8_t _manifest_buf[SUIT_MANIFEST_BUFSIZE];
|
||||||
|
|
||||||
#ifdef MODULE_SUIT_V4
|
#ifdef MODULE_SUIT
|
||||||
static inline void _print_download_progress(size_t offset, size_t len, uint32_t image_size)
|
static inline void _print_download_progress(size_t offset, size_t len,
|
||||||
|
uint32_t image_size)
|
||||||
{
|
{
|
||||||
(void)offset;
|
(void)offset;
|
||||||
(void)len;
|
(void)len;
|
||||||
@ -145,6 +146,7 @@ static inline uint32_t deadline_from_interval(int32_t interval)
|
|||||||
static inline uint32_t deadline_left(uint32_t deadline)
|
static inline uint32_t deadline_left(uint32_t deadline)
|
||||||
{
|
{
|
||||||
int32_t left = (int32_t)(deadline - _now());
|
int32_t left = (int32_t)(deadline - _now());
|
||||||
|
|
||||||
if (left < 0) {
|
if (left < 0) {
|
||||||
left = 0;
|
left = 0;
|
||||||
}
|
}
|
||||||
@ -155,7 +157,7 @@ static ssize_t _nanocoap_request(sock_udp_t *sock, coap_pkt_t *pkt, size_t len)
|
|||||||
{
|
{
|
||||||
ssize_t res = -EAGAIN;
|
ssize_t res = -EAGAIN;
|
||||||
size_t pdu_len = (pkt->payload - (uint8_t *)pkt->hdr) + pkt->payload_len;
|
size_t pdu_len = (pkt->payload - (uint8_t *)pkt->hdr) + pkt->payload_len;
|
||||||
uint8_t *buf = (uint8_t*)pkt->hdr;
|
uint8_t *buf = (uint8_t *)pkt->hdr;
|
||||||
uint32_t id = coap_get_id(pkt);
|
uint32_t id = coap_get_id(pkt);
|
||||||
|
|
||||||
/* TODO: timeout random between between ACK_TIMEOUT and (ACK_TIMEOUT *
|
/* TODO: timeout random between between ACK_TIMEOUT and (ACK_TIMEOUT *
|
||||||
@ -163,7 +165,9 @@ static ssize_t _nanocoap_request(sock_udp_t *sock, coap_pkt_t *pkt, size_t len)
|
|||||||
uint32_t timeout = COAP_ACK_TIMEOUT * US_PER_SEC;
|
uint32_t timeout = COAP_ACK_TIMEOUT * US_PER_SEC;
|
||||||
uint32_t deadline = deadline_from_interval(timeout);
|
uint32_t deadline = deadline_from_interval(timeout);
|
||||||
|
|
||||||
unsigned tries_left = COAP_MAX_RETRANSMIT + 1; /* add 1 for initial transmit */
|
/* add 1 for initial transmit */
|
||||||
|
unsigned tries_left = COAP_MAX_RETRANSMIT + 1;
|
||||||
|
|
||||||
while (tries_left) {
|
while (tries_left) {
|
||||||
if (res == -EAGAIN) {
|
if (res == -EAGAIN) {
|
||||||
res = sock_udp_send(sock, buf, pdu_len, NULL);
|
res = sock_udp_send(sock, buf, pdu_len, NULL);
|
||||||
@ -210,14 +214,19 @@ static ssize_t _nanocoap_request(sock_udp_t *sock, coap_pkt_t *pkt, size_t len)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _fetch_block(coap_pkt_t *pkt, uint8_t *buf, sock_udp_t *sock, const char *path, coap_blksize_t blksize, size_t num)
|
static int _fetch_block(coap_pkt_t *pkt, uint8_t *buf, sock_udp_t *sock,
|
||||||
|
const char *path, coap_blksize_t blksize, size_t num)
|
||||||
{
|
{
|
||||||
uint8_t *pktpos = buf;
|
uint8_t *pktpos = buf;
|
||||||
|
|
||||||
pkt->hdr = (coap_hdr_t *)buf;
|
pkt->hdr = (coap_hdr_t *)buf;
|
||||||
|
|
||||||
pktpos += coap_build_hdr(pkt->hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, num);
|
pktpos += coap_build_hdr(pkt->hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET,
|
||||||
|
num);
|
||||||
pktpos += coap_opt_put_uri_path(pktpos, 0, path);
|
pktpos += coap_opt_put_uri_path(pktpos, 0, path);
|
||||||
pktpos += coap_opt_put_uint(pktpos, COAP_OPT_URI_PATH, COAP_OPT_BLOCK2, (num << 4) | blksize);
|
pktpos +=
|
||||||
|
coap_opt_put_uint(pktpos, COAP_OPT_URI_PATH, COAP_OPT_BLOCK2,
|
||||||
|
(num << 4) | blksize);
|
||||||
|
|
||||||
pkt->payload = pktpos;
|
pkt->payload = pktpos;
|
||||||
pkt->payload_len = 0;
|
pkt->payload_len = 0;
|
||||||
@ -248,14 +257,12 @@ int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path,
|
|||||||
/* HACK: use random local port */
|
/* HACK: use random local port */
|
||||||
local.port = 0x8000 + (xtimer_now_usec() % 0XFFF);
|
local.port = 0x8000 + (xtimer_now_usec() % 0XFFF);
|
||||||
|
|
||||||
|
|
||||||
sock_udp_t sock;
|
sock_udp_t sock;
|
||||||
int res = sock_udp_create(&sock, &local, remote, 0);
|
int res = sock_udp_create(&sock, &local, remote, 0);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int more = 1;
|
int more = 1;
|
||||||
size_t num = 0;
|
size_t num = 0;
|
||||||
res = -1;
|
res = -1;
|
||||||
@ -269,7 +276,8 @@ int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path,
|
|||||||
coap_get_block2(&pkt, &block2);
|
coap_get_block2(&pkt, &block2);
|
||||||
more = block2.more;
|
more = block2.more;
|
||||||
|
|
||||||
if (callback(arg, block2.offset, pkt.payload, pkt.payload_len, more)) {
|
if (callback(arg, block2.offset, pkt.payload, pkt.payload_len,
|
||||||
|
more)) {
|
||||||
DEBUG("callback res != 0, aborting.\n");
|
DEBUG("callback res != 0, aborting.\n");
|
||||||
res = -1;
|
res = -1;
|
||||||
goto out;
|
goto out;
|
||||||
@ -349,22 +357,24 @@ ssize_t suit_coap_get_blockwise_url_buf(const char *url,
|
|||||||
coap_blksize_t blksize,
|
coap_blksize_t blksize,
|
||||||
uint8_t *buf, size_t len)
|
uint8_t *buf, size_t len)
|
||||||
{
|
{
|
||||||
_buf_t _buf = { .ptr=buf, .len=len };
|
_buf_t _buf = { .ptr = buf, .len = len };
|
||||||
int res = suit_coap_get_blockwise_url(url, blksize, _2buf, &_buf);
|
int res = suit_coap_get_blockwise_url(url, blksize, _2buf, &_buf);
|
||||||
|
|
||||||
return (res < 0) ? (ssize_t)res : (ssize_t)_buf.offset;
|
return (res < 0) ? (ssize_t)res : (ssize_t)_buf.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _suit_handle_url(const char *url)
|
static void _suit_handle_url(const char *url)
|
||||||
{
|
{
|
||||||
LOG_INFO("suit_coap: downloading \"%s\"\n", url);
|
LOG_INFO("suit_coap: downloading \"%s\"\n", url);
|
||||||
ssize_t size = suit_coap_get_blockwise_url_buf(url, COAP_BLOCKSIZE_64, _manifest_buf,
|
ssize_t size = suit_coap_get_blockwise_url_buf(url, COAP_BLOCKSIZE_64,
|
||||||
|
_manifest_buf,
|
||||||
SUIT_MANIFEST_BUFSIZE);
|
SUIT_MANIFEST_BUFSIZE);
|
||||||
if (size >= 0) {
|
if (size >= 0) {
|
||||||
LOG_INFO("suit_coap: got manifest with size %u\n", (unsigned)size);
|
LOG_INFO("suit_coap: got manifest with size %u\n", (unsigned)size);
|
||||||
|
|
||||||
riotboot_flashwrite_t writer;
|
riotboot_flashwrite_t writer;
|
||||||
#ifdef MODULE_SUIT_V4
|
#ifdef MODULE_SUIT
|
||||||
suit_v4_manifest_t manifest;
|
suit_manifest_t manifest;
|
||||||
memset(&manifest, 0, sizeof(manifest));
|
memset(&manifest, 0, sizeof(manifest));
|
||||||
|
|
||||||
manifest.writer = &writer;
|
manifest.writer = &writer;
|
||||||
@ -372,18 +382,18 @@ static void _suit_handle_url(const char *url)
|
|||||||
manifest.urlbuf_len = SUIT_URL_MAX;
|
manifest.urlbuf_len = SUIT_URL_MAX;
|
||||||
|
|
||||||
int res;
|
int res;
|
||||||
if ((res = suit_v4_parse(&manifest, _manifest_buf, size)) != SUIT_OK) {
|
if ((res = suit_parse(&manifest, _manifest_buf, size)) != SUIT_OK) {
|
||||||
LOG_INFO("suit_v4_parse() failed. res=%i\n", res);
|
LOG_INFO("suit_parse() failed. res=%i\n", res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("suit_v4_parse() success\n");
|
LOG_INFO("suit_parse() success\n");
|
||||||
if (!(manifest.state & SUIT_MANIFEST_HAVE_IMAGE)) {
|
if (!(manifest.state & SUIT_MANIFEST_HAVE_IMAGE)) {
|
||||||
LOG_INFO("manifest parsed, but no image fetched\n");
|
LOG_INFO("manifest parsed, but no image fetched\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = suit_v4_policy_check(&manifest);
|
res = suit_policy_check(&manifest);
|
||||||
if (res) {
|
if (res) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -393,7 +403,8 @@ static void _suit_handle_url(const char *url)
|
|||||||
LOG_INFO("suit_coap: finalizing image flash\n");
|
LOG_INFO("suit_coap: finalizing image flash\n");
|
||||||
riotboot_flashwrite_finish(&writer);
|
riotboot_flashwrite_finish(&writer);
|
||||||
|
|
||||||
const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_other());
|
const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(
|
||||||
|
riotboot_slot_other());
|
||||||
riotboot_hdr_print(hdr);
|
riotboot_hdr_print(hdr);
|
||||||
xtimer_sleep(1);
|
xtimer_sleep(1);
|
||||||
|
|
||||||
@ -414,7 +425,7 @@ static void _suit_handle_url(const char *url)
|
|||||||
int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len,
|
int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len,
|
||||||
int more)
|
int more)
|
||||||
{
|
{
|
||||||
suit_v4_manifest_t *manifest = (suit_v4_manifest_t *)arg;
|
suit_manifest_t *manifest = (suit_manifest_t *)arg;
|
||||||
riotboot_flashwrite_t *writer = manifest->writer;
|
riotboot_flashwrite_t *writer = manifest->writer;
|
||||||
|
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
@ -428,7 +439,8 @@ int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (writer->offset != offset) {
|
if (writer->offset != offset) {
|
||||||
LOG_WARNING("_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n",
|
LOG_WARNING(
|
||||||
|
"_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n",
|
||||||
(unsigned)writer->offset, (unsigned)offset);
|
(unsigned)writer->offset, (unsigned)offset);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -485,6 +497,7 @@ static ssize_t _slot_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
|
|||||||
{
|
{
|
||||||
/* context is passed either as NULL or 0x1 for /active or /inactive */
|
/* context is passed either as NULL or 0x1 for /active or /inactive */
|
||||||
char c = '0';
|
char c = '0';
|
||||||
|
|
||||||
if (context) {
|
if (context) {
|
||||||
c += riotboot_slot_other();
|
c += riotboot_slot_other();
|
||||||
}
|
}
|
||||||
@ -509,7 +522,7 @@ static ssize_t _trigger_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
code = COAP_CODE_CREATED;
|
code = COAP_CODE_CREATED;
|
||||||
LOG_INFO("suit: received URL: \"%s\"\n", (char*)pkt->payload);
|
LOG_INFO("suit: received URL: \"%s\"\n", (char *)pkt->payload);
|
||||||
suit_coap_trigger(pkt->payload, payload_len);
|
suit_coap_trigger(pkt->payload, payload_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,9 +545,10 @@ void suit_coap_trigger(const uint8_t *url, size_t len)
|
|||||||
static const coap_resource_t _subtree[] = {
|
static const coap_resource_t _subtree[] = {
|
||||||
#ifdef MODULE_RIOTBOOT_SLOT
|
#ifdef MODULE_RIOTBOOT_SLOT
|
||||||
{ "/suit/slot/active", COAP_METHOD_GET, _slot_handler, NULL },
|
{ "/suit/slot/active", COAP_METHOD_GET, _slot_handler, NULL },
|
||||||
{ "/suit/slot/inactive", COAP_METHOD_GET, _slot_handler, (void*)0x1 },
|
{ "/suit/slot/inactive", COAP_METHOD_GET, _slot_handler, (void *)0x1 },
|
||||||
#endif
|
#endif
|
||||||
{ "/suit/trigger", COAP_METHOD_PUT | COAP_METHOD_POST, _trigger_handler, NULL },
|
{ "/suit/trigger", COAP_METHOD_PUT | COAP_METHOD_POST, _trigger_handler,
|
||||||
|
NULL },
|
||||||
{ "/suit/version", COAP_METHOD_GET, _version_handler, NULL },
|
{ "/suit/version", COAP_METHOD_GET, _version_handler, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
81
sys/suit/transport/mock.c
Normal file
81
sys/suit/transport/mock.c
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "kernel_defines.h"
|
||||||
|
#include "cpu_conf.h"
|
||||||
|
|
||||||
|
#include "riotboot/flashwrite.h"
|
||||||
|
#include "riotboot/hdr.h"
|
||||||
|
|
||||||
|
#define SLOT0_OFFSET 0x1000
|
||||||
|
#define SLOT1_OFFSET 0x2000
|
||||||
|
|
||||||
|
static riotboot_hdr_t _riotboot_slots[] = {
|
||||||
|
{ .magic_number = RIOTBOOT_MAGIC,
|
||||||
|
.version = 1,
|
||||||
|
.start_addr=0x100000,
|
||||||
|
},
|
||||||
|
{ .magic_number = RIOTBOOT_MAGIC,
|
||||||
|
.version = 2,
|
||||||
|
.start_addr=0x200000,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const riotboot_hdr_t * const riotboot_slots[] = {
|
||||||
|
&_riotboot_slots[0],
|
||||||
|
&_riotboot_slots[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned riotboot_slot_numof = ARRAY_SIZE(riotboot_slots);
|
||||||
|
|
||||||
|
static int _current_slot;
|
||||||
|
|
||||||
|
int riotboot_slot_current(void)
|
||||||
|
{
|
||||||
|
return _current_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int riotboot_slot_other(void)
|
||||||
|
{
|
||||||
|
return (_current_slot == 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const riotboot_hdr_t *riotboot_slot_get_hdr(unsigned slot)
|
||||||
|
{
|
||||||
|
assert(slot < riotboot_slot_numof);
|
||||||
|
|
||||||
|
return riotboot_slots[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t riotboot_slot_offset(unsigned slot)
|
||||||
|
{
|
||||||
|
return (slot == 0) ? SLOT0_OFFSET : SLOT1_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
int riotboot_flashwrite_init_raw(riotboot_flashwrite_t *state, int target_slot,
|
||||||
|
size_t offset)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
(void)target_slot;
|
||||||
|
(void)offset;
|
||||||
|
puts("riotboot_flashwrite_init_raw() empty mock");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int riotboot_flashwrite_verify_sha256(const uint8_t *sha256_digest,
|
||||||
|
size_t img_size, int target_slot)
|
||||||
|
{
|
||||||
|
(void)sha256_digest;
|
||||||
|
(void)img_size;
|
||||||
|
(void)target_slot;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -1,188 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Freie Universität Berlin
|
|
||||||
* Copyright (C) 2018 Inria
|
|
||||||
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @ingroup sys_suit
|
|
||||||
* @{
|
|
||||||
*
|
|
||||||
* @file
|
|
||||||
* @brief SUIT manifest parser library for CBOR based manifests
|
|
||||||
*
|
|
||||||
* @author Koen Zandberg <koen@bergzand.net>
|
|
||||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
|
||||||
*
|
|
||||||
* @}
|
|
||||||
*/
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "suit/v4/handlers.h"
|
|
||||||
#include "suit/v4/suit.h"
|
|
||||||
#include "suit/v4/policy.h"
|
|
||||||
#include "nanocbor/nanocbor.h"
|
|
||||||
#include "cose/sign.h"
|
|
||||||
|
|
||||||
#include "public_key.h"
|
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
#define ENABLE_DEBUG (0)
|
|
||||||
#include "debug.h"
|
|
||||||
|
|
||||||
static suit_manifest_handler_t _manifest_get_auth_wrapper_handler(int key);
|
|
||||||
typedef suit_manifest_handler_t (*suit_manifest_handler_getter_t)(int key);
|
|
||||||
|
|
||||||
int suit_cbor_subparse(nanocbor_value_t *bseq, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
const uint8_t *bytes;
|
|
||||||
size_t bytes_len = 0;
|
|
||||||
int res = nanocbor_get_bstr(bseq, &bytes, &bytes_len);
|
|
||||||
if (res < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
nanocbor_decoder_init(it, bytes, bytes_len);
|
|
||||||
return SUIT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _v4_parse(suit_v4_manifest_t *manifest, const uint8_t *buf,
|
|
||||||
size_t len, suit_manifest_handler_getter_t getter)
|
|
||||||
{
|
|
||||||
nanocbor_value_t it, map;
|
|
||||||
|
|
||||||
nanocbor_decoder_init(&it, buf, len);
|
|
||||||
|
|
||||||
map = it;
|
|
||||||
|
|
||||||
if (nanocbor_enter_map(&map, &it) < 0) {
|
|
||||||
LOG_DEBUG("suit _v4_parse(): manifest not a map!\n");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!nanocbor_at_end(&it)) {
|
|
||||||
int32_t integer_key;
|
|
||||||
|
|
||||||
if (nanocbor_get_int32(&it, &integer_key) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
LOG_DEBUG("got key val=%" PRIi32 "\n", integer_key);
|
|
||||||
suit_manifest_handler_t handler = getter(integer_key);
|
|
||||||
|
|
||||||
nanocbor_value_t value = it;
|
|
||||||
nanocbor_skip(&it);
|
|
||||||
|
|
||||||
if (handler) {
|
|
||||||
int res = handler(manifest, integer_key, &value);
|
|
||||||
if (res < 0) {
|
|
||||||
LOG_INFO("handler returned <0\n)");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_DEBUG("no handler found\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nanocbor_leave_container(&map, &it);
|
|
||||||
|
|
||||||
return SUIT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int suit_v4_parse(suit_v4_manifest_t *manifest, const uint8_t *buf,
|
|
||||||
size_t len)
|
|
||||||
{
|
|
||||||
manifest->buf = buf;
|
|
||||||
manifest->len = len;
|
|
||||||
return _v4_parse(manifest, buf, len, _manifest_get_auth_wrapper_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _auth_handler(suit_v4_manifest_t *manifest, int key,
|
|
||||||
nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
const uint8_t *cose_buf;
|
|
||||||
size_t cose_len = 0;
|
|
||||||
int res = nanocbor_get_bstr(it, &cose_buf, &cose_len);
|
|
||||||
|
|
||||||
if (res < 0) {
|
|
||||||
LOG_INFO("Unable to get COSE signature\n");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
res = cose_sign_decode(&manifest->verify, cose_buf, cose_len);
|
|
||||||
if (res < 0) {
|
|
||||||
LOG_INFO("Unable to parse COSE signature\n");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _manifest_handler(suit_v4_manifest_t *manifest, int key,
|
|
||||||
nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
const uint8_t *manifest_buf;
|
|
||||||
size_t manifest_len;
|
|
||||||
|
|
||||||
nanocbor_get_bstr(it, &manifest_buf, &manifest_len);
|
|
||||||
|
|
||||||
/* Validate the COSE struct first now that we have the payload */
|
|
||||||
cose_sign_decode_set_payload(&manifest->verify, manifest_buf, manifest_len);
|
|
||||||
|
|
||||||
/* Iterate over signatures, should only be a single signature */
|
|
||||||
cose_signature_dec_t signature;
|
|
||||||
|
|
||||||
cose_sign_signature_iter_init(&signature);
|
|
||||||
if (!cose_sign_signature_iter(&manifest->verify, &signature)) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize key from hardcoded public key */
|
|
||||||
cose_key_t pkey;
|
|
||||||
cose_key_init(&pkey);
|
|
||||||
cose_key_set_keys(&pkey, COSE_EC_CURVE_ED25519, COSE_ALGO_EDDSA,
|
|
||||||
public_key, NULL, NULL);
|
|
||||||
|
|
||||||
LOG_INFO("suit: verifying manifest signature...\n");
|
|
||||||
int verification = cose_sign_verify(&manifest->verify, &signature,
|
|
||||||
&pkey, manifest->validation_buf,
|
|
||||||
SUIT_COSE_BUF_SIZE);
|
|
||||||
if (verification != 0) {
|
|
||||||
LOG_INFO("Unable to validate signature\n");
|
|
||||||
return SUIT_ERR_SIGNATURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _v4_parse(manifest, manifest_buf,
|
|
||||||
manifest_len, suit_manifest_get_manifest_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
static suit_manifest_handler_t _suit_manifest_get_handler(int key,
|
|
||||||
const suit_manifest_handler_t *handlers,
|
|
||||||
size_t len)
|
|
||||||
{
|
|
||||||
if (key < 0 || (size_t)key >= len) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return handlers[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* begin{code-style-ignore} */
|
|
||||||
static const suit_manifest_handler_t _auth_handlers[] = {
|
|
||||||
[ 0] = NULL,
|
|
||||||
[ 1] = _auth_handler,
|
|
||||||
[ 2] = _manifest_handler,
|
|
||||||
};
|
|
||||||
/* end{code-style-ignore} */
|
|
||||||
|
|
||||||
static const unsigned _auth_handlers_len = ARRAY_SIZE(_auth_handlers);
|
|
||||||
|
|
||||||
static suit_manifest_handler_t _manifest_get_auth_wrapper_handler(int key)
|
|
||||||
{
|
|
||||||
return _suit_manifest_get_handler(key, _auth_handlers,
|
|
||||||
_auth_handlers_len);
|
|
||||||
}
|
|
||||||
@ -1,516 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 Koen Zandberg
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @ingroup sys_suit_v4
|
|
||||||
* @{
|
|
||||||
*
|
|
||||||
* @file
|
|
||||||
* @brief SUIT v4
|
|
||||||
*
|
|
||||||
* @author Koen Zandberg <koen@bergzand.net>
|
|
||||||
*
|
|
||||||
* @}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
#include "suit/coap.h"
|
|
||||||
#include "suit/conditions.h"
|
|
||||||
#include "suit/v4/suit.h"
|
|
||||||
#include "suit/v4/handlers.h"
|
|
||||||
#include "suit/v4/policy.h"
|
|
||||||
#include "suit/v4/suit.h"
|
|
||||||
#include "riotboot/hdr.h"
|
|
||||||
#include "riotboot/slot.h"
|
|
||||||
#include <nanocbor/nanocbor.h>
|
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
#define HELLO_HANDLER_MAX_STRLEN 32
|
|
||||||
|
|
||||||
static int _handle_command_sequence(suit_v4_manifest_t *manifest, nanocbor_value_t *it,
|
|
||||||
suit_manifest_handler_t handler);
|
|
||||||
static int _common_handler(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it);
|
|
||||||
static int _common_sequence_handler(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it);
|
|
||||||
|
|
||||||
static int _validate_uuid(suit_v4_manifest_t *manifest, nanocbor_value_t *it, uuid_t *uuid)
|
|
||||||
{
|
|
||||||
(void)manifest;
|
|
||||||
const uint8_t *uuid_manifest_ptr;
|
|
||||||
size_t len = sizeof(uuid_t);
|
|
||||||
char uuid_str[UUID_STR_LEN + 1];
|
|
||||||
char uuid_str2[UUID_STR_LEN + 1];
|
|
||||||
if (nanocbor_get_bstr(it, &uuid_manifest_ptr, &len) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid_to_string((uuid_t *)uuid_manifest_ptr, uuid_str);
|
|
||||||
uuid_to_string(uuid, uuid_str2);
|
|
||||||
LOG_INFO("Comparing %s to %s from manifest\n", uuid_str2, uuid_str);
|
|
||||||
return uuid_equal(uuid, (uuid_t *)uuid_manifest_ptr) ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _cond_vendor_handler(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
LOG_INFO("validating vendor ID\n");
|
|
||||||
int rc = _validate_uuid(manifest, it, suit_get_vendor_id());
|
|
||||||
if (rc == SUIT_OK) {
|
|
||||||
LOG_INFO("validating vendor ID: OK\n");
|
|
||||||
manifest->validated |= SUIT_VALIDATED_VENDOR;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _cond_class_handler(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
LOG_INFO("validating class id\n");
|
|
||||||
int rc = _validate_uuid(manifest, it, suit_get_class_id());
|
|
||||||
if (rc == SUIT_OK) {
|
|
||||||
LOG_INFO("validating class id: OK\n");
|
|
||||||
manifest->validated |= SUIT_VALIDATED_CLASS;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _cond_comp_offset(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)manifest;
|
|
||||||
(void)key;
|
|
||||||
uint32_t offset;
|
|
||||||
nanocbor_get_uint32(it, &offset);
|
|
||||||
uint32_t other_offset = (uint32_t)riotboot_slot_offset(riotboot_slot_other());
|
|
||||||
LOG_INFO("Comparing manifest offset %u with other slot offset %u\n",
|
|
||||||
(unsigned)offset, (unsigned)other_offset);
|
|
||||||
return other_offset == offset ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _dtv_set_comp_idx(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
if (nanocbor_get_type(it) == NANOCBOR_TYPE_FLOAT) {
|
|
||||||
LOG_DEBUG("_dtv_set_comp_idx() ignoring boolean and floats\n)");
|
|
||||||
nanocbor_skip(it);
|
|
||||||
}
|
|
||||||
else if (nanocbor_get_int32(it, &manifest->component_current) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
LOG_DEBUG("Setting component index to %d\n", (int)manifest->component_current);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _dtv_run_seq_cond(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
LOG_DEBUG("Starting conditional sequence handler\n");
|
|
||||||
_handle_command_sequence(manifest, it, _common_sequence_handler);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _param_get_uri_list(suit_v4_manifest_t *manifest, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
LOG_DEBUG("got url list\n");
|
|
||||||
manifest->components[manifest->component_current].url = *it;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
static int _param_get_digest(suit_v4_manifest_t *manifest, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
LOG_DEBUG("got digest\n");
|
|
||||||
manifest->components[manifest->component_current].digest = *it;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _param_get_img_size(suit_v4_manifest_t *manifest, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
int res = nanocbor_get_uint32(it, &manifest->components[0].size);
|
|
||||||
if (res < 0) {
|
|
||||||
LOG_DEBUG("error getting image size\n");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _dtv_set_param(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
/* `it` points to the entry of the map containing the type and value */
|
|
||||||
nanocbor_value_t map;
|
|
||||||
|
|
||||||
nanocbor_enter_map(it, &map);
|
|
||||||
|
|
||||||
while (!nanocbor_at_end(&map)) {
|
|
||||||
/* map points to the key of the param */
|
|
||||||
int32_t param_key;
|
|
||||||
nanocbor_get_int32(&map, ¶m_key);
|
|
||||||
LOG_DEBUG("Setting component index to %" PRIi32 "\n", manifest->component_current);
|
|
||||||
LOG_DEBUG("param_key=%" PRIi32 "\n", param_key);
|
|
||||||
int res;
|
|
||||||
switch (param_key) {
|
|
||||||
case 6: /* SUIT URI LIST */
|
|
||||||
res = _param_get_uri_list(manifest, &map);
|
|
||||||
break;
|
|
||||||
case 11: /* SUIT DIGEST */
|
|
||||||
res = _param_get_digest(manifest, &map);
|
|
||||||
break;
|
|
||||||
case 12: /* SUIT IMAGE SIZE */
|
|
||||||
res = _param_get_img_size(manifest, &map);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nanocbor_skip(&map);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SUIT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _dtv_fetch(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *_it)
|
|
||||||
{
|
|
||||||
(void)key; (void)_it; (void)manifest;
|
|
||||||
LOG_DEBUG("_dtv_fetch() key=%i\n", key);
|
|
||||||
|
|
||||||
const uint8_t *url;
|
|
||||||
size_t url_len;
|
|
||||||
|
|
||||||
/* TODO: there must be a simpler way */
|
|
||||||
{
|
|
||||||
/* the url list is a binary sequence containing a cbor array of
|
|
||||||
* (priority, url) tuples (represented as array with length two)
|
|
||||||
* */
|
|
||||||
|
|
||||||
nanocbor_value_t it;
|
|
||||||
|
|
||||||
/* open sequence with cbor parser */
|
|
||||||
int err = suit_cbor_subparse(&manifest->components[0].url, &it);
|
|
||||||
if (err < 0) {
|
|
||||||
LOG_DEBUG("subparse failed\n)");
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
nanocbor_value_t url_it;
|
|
||||||
/* enter container, confirm it is an array, too */
|
|
||||||
if (nanocbor_enter_array(&it, &url_it) < 0) {
|
|
||||||
LOG_DEBUG("url list no array\n)");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
nanocbor_value_t url_value_it;
|
|
||||||
if (nanocbor_enter_array(&url_it, &url_value_it) < 0) {
|
|
||||||
LOG_DEBUG("url entry no array\n)");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expect two entries: priority as int, url as byte string. bail out if not. */
|
|
||||||
uint32_t prio;
|
|
||||||
/* check that first array entry is an int (the priority of the url) */
|
|
||||||
if (nanocbor_get_uint32(&url_value_it, &prio) < 0) {
|
|
||||||
LOG_DEBUG("expected URL priority (int), got %d\n",
|
|
||||||
nanocbor_get_type(&url_value_it));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
LOG_DEBUG("URL priority %"PRIu32"\n", prio);
|
|
||||||
|
|
||||||
int res = nanocbor_get_tstr(&url_value_it, &url, &url_len);
|
|
||||||
if (res < 0) {
|
|
||||||
LOG_DEBUG("error parsing URL\n)");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
if (url_len >= manifest->urlbuf_len) {
|
|
||||||
LOG_INFO("url too large: %u>%u\n)", (unsigned)url_len,
|
|
||||||
(unsigned)manifest->urlbuf_len);
|
|
||||||
return SUIT_ERR_UNSUPPORTED;
|
|
||||||
}
|
|
||||||
memcpy(manifest->urlbuf, url, url_len);
|
|
||||||
manifest->urlbuf[url_len] = '\0';
|
|
||||||
|
|
||||||
nanocbor_leave_container(&url_it, &url_value_it);
|
|
||||||
nanocbor_leave_container(&it, &url_it);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("_dtv_fetch() fetching \"%s\" (url_len=%u)\n", manifest->urlbuf,
|
|
||||||
(unsigned)url_len);
|
|
||||||
|
|
||||||
int target_slot = riotboot_slot_other();
|
|
||||||
riotboot_flashwrite_init(manifest->writer, target_slot);
|
|
||||||
int res = suit_coap_get_blockwise_url(manifest->urlbuf, COAP_BLOCKSIZE_64,
|
|
||||||
suit_flashwrite_helper, manifest);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
LOG_INFO("image download failed\n)");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t *digest;
|
|
||||||
size_t digest_len;
|
|
||||||
|
|
||||||
res = nanocbor_get_bstr(&manifest->components[0].digest, &digest, &digest_len);
|
|
||||||
if (res < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* "digest" points to a 36 byte string that includes the digest type.
|
|
||||||
* riotboot_flashwrite_verify_sha256() is only interested in the 32b digest,
|
|
||||||
* so shift the pointer accordingly.
|
|
||||||
*/
|
|
||||||
res = riotboot_flashwrite_verify_sha256(digest + 4, manifest->components[0].size,
|
|
||||||
target_slot);
|
|
||||||
if (res) {
|
|
||||||
LOG_INFO("image verification failed\n");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest->state |= SUIT_MANIFEST_HAVE_IMAGE;
|
|
||||||
|
|
||||||
return SUIT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _version_handler(suit_v4_manifest_t *manifest, int key,
|
|
||||||
nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)manifest;
|
|
||||||
(void)key;
|
|
||||||
/* Validate manifest version */
|
|
||||||
int32_t version = -1;
|
|
||||||
if (nanocbor_get_int32(it, &version) >= 0) {
|
|
||||||
if (version == SUIT_VERSION) {
|
|
||||||
manifest->validated |= SUIT_VALIDATED_VERSION;
|
|
||||||
LOG_INFO("suit: validated manifest version\n)");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _seq_no_handler(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
|
|
||||||
int32_t seq_nr;
|
|
||||||
|
|
||||||
if (nanocbor_get_int32(it, &seq_nr) < 0) {
|
|
||||||
LOG_INFO("Unable to get sequence number\n");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(riotboot_slot_current());
|
|
||||||
if (seq_nr <= (int32_t)hdr->version) {
|
|
||||||
LOG_INFO("%"PRId32" <= %"PRId32"\n", seq_nr, hdr->version);
|
|
||||||
LOG_INFO("seq_nr <= running image\n)");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr = riotboot_slot_get_hdr(riotboot_slot_other());
|
|
||||||
if (riotboot_hdr_validate(hdr) == 0) {
|
|
||||||
if (seq_nr<= (int32_t)hdr->version) {
|
|
||||||
LOG_INFO("%"PRIu32" <= %"PRIu32"\n", seq_nr, hdr->version);
|
|
||||||
LOG_INFO("seq_nr <= other image\n)");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG_INFO("suit: validated sequence number\n)");
|
|
||||||
manifest->validated |= SUIT_VALIDATED_SEQ_NR;
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _dependencies_handler(suit_v4_manifest_t *manifest, int key,
|
|
||||||
nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)manifest;
|
|
||||||
(void)key;
|
|
||||||
(void)it;
|
|
||||||
/* No dependency support */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _component_handler(suit_v4_manifest_t *manifest, int key,
|
|
||||||
nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)manifest;
|
|
||||||
(void)key;
|
|
||||||
|
|
||||||
nanocbor_value_t arr;
|
|
||||||
|
|
||||||
LOG_DEBUG("storing components\n)");
|
|
||||||
if (nanocbor_enter_array(it, &arr) < 0) {
|
|
||||||
LOG_DEBUG("components field not an array\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
unsigned n = 0;
|
|
||||||
while (!nanocbor_at_end(&arr)) {
|
|
||||||
nanocbor_value_t map;
|
|
||||||
if (n < SUIT_V4_COMPONENT_MAX) {
|
|
||||||
manifest->components_len += 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_DEBUG("too many components\n)");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nanocbor_enter_map(&arr, &map) < 0) {
|
|
||||||
LOG_DEBUG("suit _v4_parse(): manifest not a map!\n");
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
suit_v4_component_t *current = &manifest->components[n];
|
|
||||||
|
|
||||||
while (!nanocbor_at_end(&map)) {
|
|
||||||
|
|
||||||
/* handle key, value */
|
|
||||||
int32_t integer_key;
|
|
||||||
if (nanocbor_get_int32(&map, &integer_key) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (integer_key) {
|
|
||||||
case SUIT_COMPONENT_IDENTIFIER:
|
|
||||||
current->identifier = map;
|
|
||||||
break;
|
|
||||||
case SUIT_COMPONENT_SIZE:
|
|
||||||
LOG_DEBUG("skipping SUIT_COMPONENT_SIZE");
|
|
||||||
break;
|
|
||||||
case SUIT_COMPONENT_DIGEST:
|
|
||||||
current->digest = map;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG_DEBUG("ignoring unexpected component data (nr. %" PRIi32 ")\n",
|
|
||||||
integer_key);
|
|
||||||
}
|
|
||||||
nanocbor_skip(&map);
|
|
||||||
|
|
||||||
LOG_DEBUG("component %u parsed\n", n);
|
|
||||||
}
|
|
||||||
nanocbor_leave_container(&arr, &map);
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest->state |= SUIT_MANIFEST_HAVE_COMPONENTS;
|
|
||||||
|
|
||||||
nanocbor_leave_container(it, &arr);
|
|
||||||
|
|
||||||
LOG_DEBUG("storing components done\n)");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* begin{code-style-ignore} */
|
|
||||||
static const suit_manifest_handler_t global_handlers[] = {
|
|
||||||
[ 0] = NULL,
|
|
||||||
[ 1] = _version_handler,
|
|
||||||
[ 2] = _seq_no_handler,
|
|
||||||
[ 3] = _dependencies_handler,
|
|
||||||
[ 4] = _component_handler,
|
|
||||||
[ 5] = NULL,
|
|
||||||
[ 6] = _common_handler,
|
|
||||||
[ 9] = _common_handler,
|
|
||||||
};
|
|
||||||
/* end{code-style-ignore} */
|
|
||||||
|
|
||||||
static const unsigned global_handlers_len = ARRAY_SIZE(global_handlers);
|
|
||||||
|
|
||||||
/* begin{code-style-ignore} */
|
|
||||||
static const suit_manifest_handler_t _sequence_handlers[] = {
|
|
||||||
[ 0] = NULL,
|
|
||||||
[ 1] = _cond_vendor_handler,
|
|
||||||
[ 2] = _cond_class_handler,
|
|
||||||
[10] = _cond_comp_offset,
|
|
||||||
/* Directives */
|
|
||||||
[11] = _dtv_set_comp_idx,
|
|
||||||
/* [12] = _dtv_set_man_idx, */
|
|
||||||
/* [13] = _dtv_run_seq, */
|
|
||||||
[14] = _dtv_run_seq_cond,
|
|
||||||
[16] = _dtv_set_param,
|
|
||||||
[20] = _dtv_fetch,
|
|
||||||
/* [22] = _dtv_run, */
|
|
||||||
};
|
|
||||||
/* end{code-style-ignore} */
|
|
||||||
|
|
||||||
static const unsigned _sequence_handlers_len = ARRAY_SIZE(_sequence_handlers);
|
|
||||||
|
|
||||||
static suit_manifest_handler_t _suit_manifest_get_handler(int key,
|
|
||||||
const suit_manifest_handler_t *handlers,
|
|
||||||
size_t len)
|
|
||||||
{
|
|
||||||
if (key < 0 || (size_t)key >= len) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return handlers[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
suit_manifest_handler_t suit_manifest_get_manifest_handler(int key)
|
|
||||||
{
|
|
||||||
return _suit_manifest_get_handler(key, global_handlers,
|
|
||||||
global_handlers_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _common_sequence_handler(suit_v4_manifest_t *manifest, int key,
|
|
||||||
nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
|
|
||||||
suit_manifest_handler_t handler = _suit_manifest_get_handler(key,
|
|
||||||
_sequence_handlers,
|
|
||||||
_sequence_handlers_len);
|
|
||||||
LOG_DEBUG("Handling handler with key %d at %p\n", key, handler);
|
|
||||||
if (handler) {
|
|
||||||
return handler(manifest, key, it);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_DEBUG("Sequence handler not implemented, ID: %d\n", key);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _common_handler(suit_v4_manifest_t *manifest, int key, nanocbor_value_t *it)
|
|
||||||
{
|
|
||||||
(void)key;
|
|
||||||
return _handle_command_sequence(manifest, it, _common_sequence_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _handle_command_sequence(suit_v4_manifest_t *manifest, nanocbor_value_t *bseq,
|
|
||||||
suit_manifest_handler_t handler)
|
|
||||||
{
|
|
||||||
|
|
||||||
LOG_DEBUG("Handling command sequence\n");
|
|
||||||
nanocbor_value_t it, arr;
|
|
||||||
|
|
||||||
int err = suit_cbor_subparse(bseq, &it);
|
|
||||||
if (err < 0) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nanocbor_enter_array(&it, &arr) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!nanocbor_at_end(&arr)) {
|
|
||||||
nanocbor_value_t map;
|
|
||||||
if (nanocbor_enter_map(&arr, &map) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
int32_t integer_key;
|
|
||||||
if (nanocbor_get_int32(&map, &integer_key) < 0) {
|
|
||||||
return SUIT_ERR_INVALID_MANIFEST;
|
|
||||||
}
|
|
||||||
int res = handler(manifest, integer_key, &map);
|
|
||||||
if (res < 0) {
|
|
||||||
LOG_DEBUG("Sequence handler error\n");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
nanocbor_leave_container(&arr, &map);
|
|
||||||
}
|
|
||||||
nanocbor_leave_container(&it, &arr);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
40
tests/suit_manifest/Makefile
Normal file
40
tests/suit_manifest/Makefile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
include ../Makefile.tests_common
|
||||||
|
|
||||||
|
USEMODULE += suit
|
||||||
|
USEMODULE += riotboot_hdr
|
||||||
|
USEMODULE += embunit
|
||||||
|
|
||||||
|
# Lots of structs on the stack and crypto verification
|
||||||
|
CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(8*THREAD_STACKSIZE_DEFAULT\)
|
||||||
|
|
||||||
|
# Add a macro for the board name without quotes to use in the include file
|
||||||
|
# generator macro
|
||||||
|
CFLAGS += -DBOARD_NAME_UNQ=$(BOARD)
|
||||||
|
|
||||||
|
# BINDIR is not included until Makefile.include is parsed
|
||||||
|
MANIFEST_DIR ?= bin/$(BOARD)/manifests
|
||||||
|
BLOBS += $(MANIFEST_DIR)/manifest0.bin
|
||||||
|
BLOBS += $(MANIFEST_DIR)/manifest1.bin
|
||||||
|
BLOBS += $(MANIFEST_DIR)/manifest2.bin
|
||||||
|
BLOBS += $(MANIFEST_DIR)/manifest3.bin
|
||||||
|
|
||||||
|
USEMODULE += suit_transport_mock
|
||||||
|
|
||||||
|
# Use a version of 'native' that includes flash page support
|
||||||
|
ifeq (native, $(BOARD))
|
||||||
|
BOARDSDIR = $(CURDIR)/native_flashpage
|
||||||
|
endif
|
||||||
|
|
||||||
|
FEATURES_REQUIRED += periph_flashpage
|
||||||
|
|
||||||
|
TEST_DATA = $(MANIFEST_DIR)/created
|
||||||
|
BUILDDEPS += $(TEST_DATA)
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.include
|
||||||
|
|
||||||
|
$(call target-export-variables,all,SUIT_TOOL SUIT_SEC MANIFEST_DIR)
|
||||||
|
|
||||||
|
$(TEST_DATA): $(SUIT_SEC) $(SUIT_PUB_HDR)
|
||||||
|
@mkdir -p $(MANIFEST_DIR)
|
||||||
|
sh create_test_data.sh
|
||||||
|
@touch $@
|
||||||
17
tests/suit_manifest/Makefile.ci
Normal file
17
tests/suit_manifest/Makefile.ci
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
BOARD_INSUFFICIENT_MEMORY := \
|
||||||
|
chronos \
|
||||||
|
i-nucleo-lrwan1 \
|
||||||
|
msb-430 \
|
||||||
|
msb-430h \
|
||||||
|
nucleo-f030r8 \
|
||||||
|
nucleo-f042k6 \
|
||||||
|
nucleo-l031k6 \
|
||||||
|
nucleo-l053r8 \
|
||||||
|
stm32f030f4-demo \
|
||||||
|
stm32l0538-disco \
|
||||||
|
stm32f0discovery \
|
||||||
|
telosb \
|
||||||
|
wsn430-v1_3b \
|
||||||
|
wsn430-v1_4 \
|
||||||
|
z1 \
|
||||||
|
#
|
||||||
47
tests/suit_manifest/create_test_data.sh
Normal file
47
tests/suit_manifest/create_test_data.sh
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
gen_manifest() {
|
||||||
|
local out="$1"
|
||||||
|
shift
|
||||||
|
local seqnr="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
|
||||||
|
"${RIOTBASE}/dist/tools/suit_v3/gen_manifest.py" \
|
||||||
|
--urlroot "test://test" \
|
||||||
|
--seqnr "$seqnr" \
|
||||||
|
--uuid-vendor "riot-os.org" \
|
||||||
|
--uuid-class "${BOARD}" \
|
||||||
|
-o "$out.tmp" \
|
||||||
|
"${1}:$((0x1000))" "${2}:$((0x2000))"
|
||||||
|
|
||||||
|
${SUIT_TOOL} create -f suit -i "$out.tmp" -o "$out"
|
||||||
|
|
||||||
|
rm -f "$out.tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
sign_manifest() {
|
||||||
|
local in="$1"
|
||||||
|
local out="$2"
|
||||||
|
|
||||||
|
"${SUIT_TOOL}" sign -k "${SUIT_SEC}" -m "$in" -o "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# random invalid data files
|
||||||
|
echo foo > "${MANIFEST_DIR}/file1.bin"
|
||||||
|
echo bar > "${MANIFEST_DIR}/file2.bin"
|
||||||
|
|
||||||
|
# random valid cbor (manifest but not signed, missing cose auth)
|
||||||
|
gen_manifest "${MANIFEST_DIR}/manifest0.bin" 1 "${MANIFEST_DIR}/file1.bin" "${MANIFEST_DIR}/file2.bin"
|
||||||
|
|
||||||
|
# manifest with invalid seqnr
|
||||||
|
sign_manifest "${MANIFEST_DIR}/manifest0.bin" "${MANIFEST_DIR}/manifest1.bin"
|
||||||
|
|
||||||
|
(BOARD=invalid gen_manifest "${MANIFEST_DIR}/manifest2.bin".unsigned 2 "${MANIFEST_DIR}/file1.bin" "${MANIFEST_DIR}/file2.bin")
|
||||||
|
sign_manifest "${MANIFEST_DIR}/manifest2.bin".unsigned "${MANIFEST_DIR}/manifest2.bin"
|
||||||
|
|
||||||
|
# valid manifest, valid seqnr, signed
|
||||||
|
gen_manifest "${MANIFEST_DIR}/manifest3.bin".unsigned 2 "${MANIFEST_DIR}/file1.bin" "${MANIFEST_DIR}/file2.bin"
|
||||||
|
sign_manifest "${MANIFEST_DIR}/manifest3.bin".unsigned "${MANIFEST_DIR}/manifest3.bin"
|
||||||
43
tests/suit_manifest/include/riotboot/flashwrite.h
Normal file
43
tests/suit_manifest/include/riotboot/flashwrite.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RIOTBOOT_FLASHWRITE_H
|
||||||
|
#define RIOTBOOT_FLASHWRITE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief mockup flashwrite context
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned target_slot; /**< Mockup slot */
|
||||||
|
} riotboot_flashwrite_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mockup flashwrite initialization function
|
||||||
|
*
|
||||||
|
* @param state flashwrite state
|
||||||
|
* @param target_slot Target slot
|
||||||
|
*
|
||||||
|
* @return Always returns 0
|
||||||
|
*/
|
||||||
|
static inline int riotboot_flashwrite_init(riotboot_flashwrite_t *state,
|
||||||
|
int target_slot)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
(void)target_slot;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* RIOTBOOT_FLASHWRITE_H */
|
||||||
106
tests/suit_manifest/main.c
Normal file
106
tests/suit_manifest/main.c
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Inria
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup tests
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief Tests for suit manifest parser module
|
||||||
|
*
|
||||||
|
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "kernel_defines.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "suit.h"
|
||||||
|
#include "embUnit.h"
|
||||||
|
|
||||||
|
#define TEST_MANIFEST_INCLUDE(file) <blob/bin/BOARD_NAME_UNQ/manifests/file>
|
||||||
|
|
||||||
|
/* cppcheck-suppress preprocessorErrorDirective
|
||||||
|
* (reason: board-dependent include paths) */
|
||||||
|
#include TEST_MANIFEST_INCLUDE(manifest0.bin.h)
|
||||||
|
#include TEST_MANIFEST_INCLUDE(manifest1.bin.h)
|
||||||
|
#include TEST_MANIFEST_INCLUDE(manifest2.bin.h)
|
||||||
|
#include TEST_MANIFEST_INCLUDE(manifest3.bin.h)
|
||||||
|
|
||||||
|
#define SUIT_URL_MAX 128
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const unsigned char *data;
|
||||||
|
size_t len;
|
||||||
|
int expected;
|
||||||
|
} manifest_blob_t;
|
||||||
|
|
||||||
|
const manifest_blob_t manifest_blobs[] = {
|
||||||
|
/* Older GCC can't handle manifestx_bin_len here */
|
||||||
|
{ manifest0_bin, sizeof(manifest0_bin), SUIT_ERR_SIGNATURE },
|
||||||
|
{ manifest1_bin, sizeof(manifest1_bin), SUIT_ERR_SEQUENCE_NUMBER },
|
||||||
|
{ manifest2_bin, sizeof(manifest2_bin), SUIT_ERR_COND },
|
||||||
|
{ manifest3_bin, sizeof(manifest3_bin), SUIT_OK },
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned manifest_blobs_numof = ARRAY_SIZE(manifest_blobs);
|
||||||
|
|
||||||
|
static int test_suit_manifest(const unsigned char *manifest_bin,
|
||||||
|
size_t manifest_bin_len)
|
||||||
|
{
|
||||||
|
char _url[SUIT_URL_MAX];
|
||||||
|
suit_manifest_t manifest;
|
||||||
|
riotboot_flashwrite_t writer;
|
||||||
|
|
||||||
|
memset(&manifest, 0, sizeof(manifest));
|
||||||
|
memset(&writer, 0, sizeof(writer));
|
||||||
|
|
||||||
|
manifest.writer = &writer;
|
||||||
|
manifest.urlbuf = _url;
|
||||||
|
manifest.urlbuf_len = SUIT_URL_MAX;
|
||||||
|
|
||||||
|
int res;
|
||||||
|
if ((res =
|
||||||
|
suit_parse(&manifest, manifest_bin,
|
||||||
|
manifest_bin_len)) != SUIT_OK) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_suit_manifest_01_manifests(void)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < manifest_blobs_numof; i++) {
|
||||||
|
printf("\n--- testing manifest %u\n", i);
|
||||||
|
int res = \
|
||||||
|
test_suit_manifest(manifest_blobs[i].data, manifest_blobs[i].len);
|
||||||
|
printf("---- res=%i (expected=%i)\n", res, manifest_blobs[i].expected);
|
||||||
|
TEST_ASSERT_EQUAL_INT(manifest_blobs[i].expected, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Test *tests_suit_manifest(void)
|
||||||
|
{
|
||||||
|
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||||
|
new_TestFixture(test_suit_manifest_01_manifests),
|
||||||
|
};
|
||||||
|
|
||||||
|
EMB_UNIT_TESTCALLER(suit_manifest_tests, NULL, NULL, fixtures);
|
||||||
|
|
||||||
|
return (Test *)&suit_manifest_tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
TESTS_START();
|
||||||
|
TESTS_RUN(tests_suit_manifest());
|
||||||
|
TESTS_END();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
MODULE := suit_v4
|
DIRS += $(RIOTBOARD)/native
|
||||||
|
|
||||||
include $(RIOTBASE)/Makefile.base
|
include $(RIOTBASE)/Makefile.base
|
||||||
1
tests/suit_manifest/native_flashpage/native/Makefile.dep
Normal file
1
tests/suit_manifest/native_flashpage/native/Makefile.dep
Normal file
@ -0,0 +1 @@
|
|||||||
|
include $(RIOTBOARD)/native/Makefile.dep
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
FEATURES_PROVIDED += periph_flashpage
|
||||||
|
|
||||||
|
include $(RIOTBOARD)/native/Makefile.features
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
CFLAGS += -DFLASHPAGE_SIZE=256
|
||||||
|
CFLAGS += -DFLASHPAGE_NUMOF=16
|
||||||
|
|
||||||
|
# We must duplicate the include done by $(RIOTBASE)/Makefile.include
|
||||||
|
# to also include the main board header
|
||||||
|
INCLUDES += $(addprefix -I,$(wildcard $(RIOTBOARD)/native/include))
|
||||||
|
|
||||||
|
include $(RIOTBOARD)/native/Makefile.include
|
||||||
23
tests/suit_manifest/tests/01-run.py
Executable file
23
tests/suit_manifest/tests/01-run.py
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (C) 2018 Francisco Acosta <francisco.acosta@inria.fr>
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
from testrunner import run
|
||||||
|
|
||||||
|
|
||||||
|
def testfunc(child):
|
||||||
|
board = os.environ['BOARD']
|
||||||
|
# Increase timeout on "real" hardware
|
||||||
|
# 16 seconds on `samr21-xpro`
|
||||||
|
timeout = 30 if board != 'native' else -1
|
||||||
|
child.expect(r"OK \(\d+ tests\)", timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run(testfunc))
|
||||||
Loading…
x
Reference in New Issue
Block a user