mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #13486 from bergzand/pr/suit/ietf_v3
SUIT: Update to draft-ietf-v3
This commit is contained in:
commit
7d0c475113
30
Makefile.dep
30
Makefile.dep
@ -953,25 +953,29 @@ ifneq (,$(filter sock_dtls, $(USEMODULE)))
|
||||
USEMODULE += sock_udp
|
||||
endif
|
||||
|
||||
ifneq (,$(filter suit_v4_%,$(USEMODULE)))
|
||||
USEMODULE += suit_v4
|
||||
endif
|
||||
|
||||
ifneq (,$(filter suit_v4,$(USEMODULE)))
|
||||
ifneq (,$(filter suit,$(USEMODULE)))
|
||||
USEPKG += nanocbor
|
||||
USEPKG += libcose
|
||||
USEMODULE += libcose_crypt_c25519
|
||||
USEMODULE += suit_conditions
|
||||
USEMODULE += uuid
|
||||
|
||||
# SUIT depends on riotboot support and some extra riotboot modules
|
||||
FEATURES_REQUIRED += riotboot
|
||||
USEMODULE += riotboot_slot
|
||||
USEMODULE += riotboot_flashwrite
|
||||
USEMODULE += riotboot_flashwrite_verify_sha256
|
||||
# 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
|
||||
FEATURES_REQUIRED += riotboot
|
||||
USEMODULE += riotboot_slot
|
||||
USEMODULE += riotboot_flashwrite
|
||||
USEMODULE += riotboot_flashwrite_verify_sha256
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter suit_conditions,$(USEMODULE)))
|
||||
USEMODULE += uuid
|
||||
ifneq (,$(filter suit_transport_%, $(USEMODULE)))
|
||||
USEMODULE += suit_transport
|
||||
endif
|
||||
|
||||
ifneq (,$(filter suit_transport_coap, $(USEMODULE)))
|
||||
USEMODULE += nanocoap
|
||||
endif
|
||||
|
||||
ifneq (,$(filter suit_%,$(USEMODULE)))
|
||||
|
@ -480,6 +480,9 @@ ELFFILE ?= $(BINDIR)/$(APPLICATION).elf
|
||||
HEXFILE ?= $(ELFFILE:.elf=.hex)
|
||||
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
|
||||
# variables defined in `riotboot.mk` for `FLASHFILE` before it is evaluated.
|
||||
# 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/uhcpd\
|
||||
|dist/tools/stm32loader\
|
||||
|dist/tools/suit_v4/suit_manifest_encoder_04)\
|
||||
|dist/tools/suit_v3/suit-manifest-generator)\
|
||||
|dist/tools/esptool"
|
||||
FILEREGEX='(\.py$|pyterm$)'
|
||||
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 += suit suit_coap
|
||||
|
||||
# SUIT draft v4 support:
|
||||
USEMODULE += suit_v4
|
||||
USEMODULE += suit suit_transport_coap
|
||||
|
||||
# Display a progress bar during firmware download
|
||||
USEMODULE += progress_bar
|
||||
@ -97,6 +94,10 @@ TEST_EXTRA_FILES += $(SLOT_RIOT_ELFS) $(SUIT_SEC) $(SUIT_PUB)
|
||||
# a terminal is opened to synchronize.
|
||||
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
|
||||
|
||||
.PHONY: host-tools
|
||||
|
@ -127,7 +127,7 @@ In another terminal, run:
|
||||
[setup-wireless]: #Setup-a-wireless-device-behind-a-border-router
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
...
|
||||
published "/home/francisco/workspace/RIOT/examples/suit_update/bin/samr21-xpro/suit_update-riot.suitv4_signed.1557135946.bin"
|
||||
as "coap://[2001:db8::1]/fw/samr21-xpro/suit_update-riot.suitv4_signed.1557135946.bin"
|
||||
published "/home/francisco/workspace/RIOT/examples/suit_update/bin/samr21-xpro/suit_update-riot.suitv4_signed.latest.bin"
|
||||
as "coap://[2001:db8::1]/fw/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.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.suitv3_signed.latest.bin"
|
||||
as "coap://[2001:db8::1]/fw/samr21-xpro/suit_update-riot.suitv3_signed.latest.bin"
|
||||
...
|
||||
|
||||
### Notify 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
|
||||
reachable via link-local `fe80::2%riot0` on the ethos interface. If it was setup as a
|
||||
wireless device it will be reachable via its global address, something like `2001:db8::7b7e:3255:1313:8d96`
|
||||
reachable via link-local EUI64 address on the ethos interface, e.g:
|
||||
|
||||
|
||||
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:
|
||||
|
||||
$ 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:
|
||||
|
||||
@ -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
|
||||
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:
|
||||
|
||||
....
|
||||
INFO # suit_coap: got manifest with size 545
|
||||
INFO # jumping into map
|
||||
INFO # )got key val=1
|
||||
INFO # handler res=0
|
||||
INFO # got key val=2
|
||||
INFO # suit: verifying manifest signature...
|
||||
suit_coap: got manifest with size 470
|
||||
suit: verifying manifest signature
|
||||
....
|
||||
|
||||
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.
|
||||
|
||||
....
|
||||
INFO # Handling handler with key 10 at 0x2b981
|
||||
INFO # Comparing manifest offset 4096 with other slot offset 4096
|
||||
....
|
||||
INFO # Handling handler with key 10 at 0x2b981
|
||||
INFO # Comparing manifest offset 133120 with other slot offset 4096
|
||||
INFO # Sequence handler error
|
||||
suit: validated manifest version
|
||||
)suit: validated sequence number
|
||||
)validating vendor ID
|
||||
Comparing 547d0d74-6d3a-5a92-9662-4881afd9407b to 547d0d74-6d3a-5a92-9662-4881afd9407b from manifest
|
||||
validating vendor ID: OK
|
||||
validating class id
|
||||
....
|
||||
|
||||
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
|
||||
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)
|
||||
2019-04-05 16:19:26,704 - INFO # handler res=0
|
||||
2019-04-05 16:19:26,705 - INFO # got key val=10
|
||||
2019-04-05 16:19:26,707 - INFO # no handler found
|
||||
2019-04-05 16:19:26,708 - INFO # got key val=12
|
||||
2019-04-05 16:19:26,709 - INFO # no handler found
|
||||
2019-04-05 16:19:26,711 - INFO # handler res=0
|
||||
2019-04-05 16:19:26,713 - INFO # suit_v4_parse() success
|
||||
2019-04-05 16:19:26,715 - INFO # SUIT policy check OK.
|
||||
2019-04-05 16:19:26,718 - INFO # suit_coap: finalizing image flash
|
||||
2019-04-05 16:19:26,725 - INFO # riotboot_flashwrite: riotboot flashing completed successfully
|
||||
2019-04-05 16:19:26,728 - INFO # Image magic_number: 0x544f4952
|
||||
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
|
||||
Verifying image digest
|
||||
riotboot: verifying digest at 0x1fffbd15 (img at: 0x1000 size: 77448)
|
||||
Verifying image digest
|
||||
riotboot: verifying digest at 0x1fffbd15 (img at: 0x1000 size: 77448)
|
||||
suit_parse() success
|
||||
SUIT policy check OK.
|
||||
suit_coap: finalizing image flash
|
||||
riotboot_flashwrite: riotboot flashing completed successfully
|
||||
Image magic_number: 0x544f4952
|
||||
Image Version: 0x5e71f662
|
||||
Image start address: 0x00001100
|
||||
Header chksum: 0x745a0376
|
||||
|
||||
|
||||
main(): This is RIOT! (Version: 2019.04-devel-606-gaa7b-ota_suit_v2)
|
||||
main(): This is RIOT! (Version: 2020.04)
|
||||
RIOT SUIT update example application
|
||||
running from slot 1
|
||||
Waiting for address autoconfiguration...
|
||||
|
||||
The slot number should have changed from after the application reboots.
|
||||
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:
|
||||
|
||||
* riotboot
|
||||
* riotboot_hdr
|
||||
* riotboot_slot
|
||||
* riotboot_flashwrite
|
||||
* suit
|
||||
* suit_coap
|
||||
* suit_v4
|
||||
* suit_transport_coap
|
||||
|
||||
#### 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
|
||||
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.
|
||||
|
||||
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:
|
||||
the image corresponds to the wrong slot.
|
||||
|
||||
The active/inactive coap resources is used so the publisher can send a manifest
|
||||
built for the inactive slot.
|
||||
|
||||
On boot the bootloader will check the riotboot_hdr and boot on the newest
|
||||
On boot the bootloader will check the `riotboot_hdr` and boot on the newest
|
||||
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`,
|
||||
but any board supporting `riotboot`, `flashpage` and with 256kB of flash should
|
||||
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
|
||||
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.
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -610,20 +636,19 @@ The following variables are defined in makefiles/suit.inc.mk:
|
||||
|
||||
The following convention is used when naming a manifest
|
||||
|
||||
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_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
|
||||
|
||||
The following default values are using for generating the manifest:
|
||||
|
||||
SUIT_VENDOR ?= RIOT
|
||||
SUIT_VERSION ?= $(APP_VER)
|
||||
SUIT_VENDOR ?= "riot-os.org"
|
||||
SUIT_SEQNR ?= $(APP_VER)
|
||||
SUIT_CLASS ?= $(BOARD)
|
||||
SUIT_KEY ?= default
|
||||
SUIT_KEY_DIR ?= $(RIOTBASE)/keys
|
||||
SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY)
|
||||
SUIT_PUB ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pub
|
||||
SUIT_SEC ?= $(SUIT_KEY_DIR)/$(SUIT_KEY).pem
|
||||
|
||||
All files (both slot binaries, both manifests, copies of manifests with
|
||||
"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.
|
||||
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_CLASS)
|
||||
- $(SUIT_VERSION)
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <string.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)
|
||||
{
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
#include "shell.h"
|
||||
|
||||
#include "suit/coap.h"
|
||||
#include "suit/transport/coap.h"
|
||||
#include "riotboot/slot.h"
|
||||
|
||||
#ifdef MODULE_PERIPH_GPIO
|
||||
|
@ -138,7 +138,7 @@ def _test_invalid_version(child, client, app_ver):
|
||||
publish(TMPDIR.name, COAP_HOST, app_ver - 1)
|
||||
notify(COAP_HOST, client, app_ver - 1)
|
||||
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")
|
||||
|
||||
|
||||
@ -146,7 +146,7 @@ def _test_invalid_signature(child, client, app_ver):
|
||||
publish(TMPDIR.name, COAP_HOST, app_ver + 1, 'invalid_keys')
|
||||
notify(COAP_HOST, client, app_ver + 1)
|
||||
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")
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@ def _test_successful_update(child, client, app_ver):
|
||||
publish(TMPDIR.name, COAP_HOST, version)
|
||||
notify(COAP_HOST, client, version)
|
||||
child.expect_exact("suit_coap: trigger received")
|
||||
child.expect_exact("suit: verifying manifest signature...")
|
||||
child.expect_exact("suit: verifying manifest signature")
|
||||
child.expect(
|
||||
r"riotboot_flashwrite: initializing update to target slot (\d+)\r\n",
|
||||
timeout=MANIFEST_TIMEOUT,
|
||||
|
@ -146,8 +146,8 @@ riotboot/flash: riotboot/flash-slot0 riotboot/flash-bootloader
|
||||
FLASHFILE = $(RIOTBOOT_EXTENDED_BIN)
|
||||
|
||||
# include suit targets
|
||||
ifneq (,$(filter suit_v4, $(USEMODULE)))
|
||||
include $(RIOTMAKE)/suit.v4.inc.mk
|
||||
ifneq (,$(filter suit, $(USEMODULE)))
|
||||
include $(RIOTMAKE)/suit.inc.mk
|
||||
endif
|
||||
|
||||
else
|
||||
|
@ -96,7 +96,7 @@ PSEUDOMODULES += stdin
|
||||
PSEUDOMODULES += stdio_ethos
|
||||
PSEUDOMODULES += stdio_cdc_acm
|
||||
PSEUDOMODULES += stdio_uart_rx
|
||||
PSEUDOMODULES += suit_%
|
||||
PSEUDOMODULES += suit_transport_%
|
||||
PSEUDOMODULES += wakaama_objects_%
|
||||
PSEUDOMODULES += zptr
|
||||
PSEUDOMODULES += ztimer%
|
||||
@ -104,9 +104,6 @@ PSEUDOMODULES += ztimer%
|
||||
# ztimer's main module is called "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()
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @defgroup sys_suit_v4 SUIT draft v4
|
||||
* @ingroup sys_suit
|
||||
* @defgroup sys_suit SUIT secure firmware OTA upgrade infrastructure
|
||||
* @ingroup sys
|
||||
* @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
|
||||
@ -19,8 +27,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SUIT_V4_SUIT_H
|
||||
#define SUIT_V4_SUIT_H
|
||||
#ifndef SUIT_H
|
||||
#define SUIT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@ -38,40 +46,46 @@ extern "C" {
|
||||
* @brief Buffer size used for Cose
|
||||
*/
|
||||
#ifndef SUIT_COSE_BUF_SIZE
|
||||
#define SUIT_COSE_BUF_SIZE (512U)
|
||||
#define SUIT_COSE_BUF_SIZE (180U)
|
||||
#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)
|
||||
|
||||
/**
|
||||
* @brief Supported SUIT manifest version
|
||||
*/
|
||||
#define SUIT_MANIFEST_VERSION (4)
|
||||
#define SUIT_COMPONENT_MAX (1U)
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
#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
|
||||
*/
|
||||
typedef enum {
|
||||
SUIT_OK = 0, /**< Manifest parsed and validated */
|
||||
SUIT_ERR_INVALID_MANIFEST = -1, /**< Unexpected CBOR structure detected */
|
||||
SUIT_ERR_UNSUPPORTED = -2, /**< Unsupported SUIT feature detected */
|
||||
SUIT_ERR_NOT_SUPPORTED = -3, /**< Unsupported manifest features detected */
|
||||
SUIT_ERR_COND = -4, /**< Conditionals evaluate to false */
|
||||
SUIT_ERR_SEQUENCE_NUMBER = -5, /**< Sequence number less or equal to
|
||||
current sequence number */
|
||||
SUIT_ERR_SIGNATURE = -6, /**< Unable to verify signature */
|
||||
} suit_v4_error_t;
|
||||
SUIT_OK = 0, /**< Manifest parsed and validated */
|
||||
SUIT_ERR_INVALID_MANIFEST = -1, /**< Unexpected CBOR structure detected */
|
||||
SUIT_ERR_UNSUPPORTED = -2, /**< Unsupported SUIT feature detected */
|
||||
SUIT_ERR_NOT_SUPPORTED = -3, /**< Unsupported features detected */
|
||||
SUIT_ERR_COND = -4, /**< Conditionals evaluate to false */
|
||||
SUIT_ERR_SEQUENCE_NUMBER = -5, /**< Sequence number less or equal to
|
||||
current sequence number */
|
||||
SUIT_ERR_SIGNATURE = -6, /**< Unable to verify signature */
|
||||
SUIT_ERR_DIGEST_MISMATCH = -7, /**< Digest mismatch with COSE and SUIT */
|
||||
} suit_error_t;
|
||||
|
||||
/**
|
||||
* @brief SUIT payload digest algorithms
|
||||
@ -84,7 +98,7 @@ typedef enum {
|
||||
SUIT_DIGEST_SHA256 = 1, /**< SHA256 */
|
||||
SUIT_DIGEST_SHA384 = 2, /**< SHA384 */
|
||||
SUIT_DIGEST_SHA512 = 3, /**< SHA512 */
|
||||
} suit_v4_digest_t;
|
||||
} suit_digest_t;
|
||||
|
||||
/**
|
||||
* @brief SUIT payload digest types
|
||||
@ -97,7 +111,7 @@ typedef enum {
|
||||
SUIT_DIGEST_TYPE_INSTALLED = 2, /**< Installed firmware digest */
|
||||
SUIT_DIGEST_TYPE_CIPHERTEXT = 3, /**< Ciphertext digest */
|
||||
SUIT_DIGEST_TYPE_PREIMAGE = 4 /**< Pre-image digest */
|
||||
} suit_v4_digest_type_t;
|
||||
} suit_digest_type_t;
|
||||
|
||||
/**
|
||||
* @brief SUIT component types
|
||||
@ -112,36 +126,35 @@ enum {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SUIT v4 component struct
|
||||
* @brief SUIT component struct
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t size; /**< Size */
|
||||
nanocbor_value_t identifier; /**< Identifier*/
|
||||
nanocbor_value_t identifier; /**< Identifier */
|
||||
nanocbor_value_t url; /**< Url */
|
||||
nanocbor_value_t digest; /**< Digest */
|
||||
} suit_v4_component_t;
|
||||
} suit_component_t;
|
||||
|
||||
/**
|
||||
* @brief SUIT manifest struct
|
||||
*/
|
||||
typedef struct {
|
||||
cose_sign_dec_t verify; /**< COSE signature validation struct */
|
||||
const uint8_t *buf; /**< ptr to the buffer 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 state; /**< bitfield holding state information */
|
||||
|
||||
/** 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 */
|
||||
int32_t component_current; /**< Current component index */
|
||||
uint32_t component_current; /**< Current component index */
|
||||
riotboot_flashwrite_t *writer; /**< Pointer to the riotboot flash writer */
|
||||
/** Manifest validation buffer */
|
||||
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 */
|
||||
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
|
||||
@ -163,9 +176,9 @@ typedef struct {
|
||||
* @param[in] len length of the manifest data in the buffer
|
||||
*
|
||||
* @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
|
||||
@ -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 -1 on invalid manifest policy
|
||||
*/
|
||||
int suit_v4_policy_check(suit_v4_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);
|
||||
int suit_policy_check(suit_manifest_t *manifest);
|
||||
|
||||
/**
|
||||
* @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 /* 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.
|
||||
*/
|
||||
/**
|
||||
* @ingroup sys_suit_v4
|
||||
* @ingroup sys_suit
|
||||
* @brief SUIT policy definitions
|
||||
*
|
||||
* @{
|
||||
@ -20,8 +20,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SUIT_V4_POLICY_H
|
||||
#define SUIT_V4_POLICY_H
|
||||
#ifndef SUIT_POLICY_H
|
||||
#define SUIT_POLICY_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@ -48,11 +48,12 @@ extern "C" {
|
||||
* @brief 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
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SUIT_V4_POLICY_H */
|
||||
#endif /* SUIT_POLICY_H */
|
||||
/** @} */
|
@ -9,11 +9,9 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup sys_suit SUIT secure firmware updates
|
||||
* @ingroup sys
|
||||
* @brief SUIT secure firmware updates
|
||||
*
|
||||
* @experimental
|
||||
* @ingroup sys_suit
|
||||
* @defgroup sys_suit_transport_coap SUIT firmware CoAP transport
|
||||
* @brief SUIT secure firmware updates over CoAP
|
||||
*
|
||||
* @{
|
||||
*
|
||||
@ -24,8 +22,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SUIT_COAP_H
|
||||
#define SUIT_COAP_H
|
||||
#ifndef SUIT_TRANSPORT_COAP_H
|
||||
#define SUIT_TRANSPORT_COAP_H
|
||||
|
||||
#include "net/nanocoap.h"
|
||||
|
||||
@ -157,5 +155,5 @@ void suit_coap_trigger(const uint8_t *url, size_t len);
|
||||
}
|
||||
#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 <inttypes.h>
|
||||
|
||||
#include "suit/coap.h"
|
||||
#include "suit/transport/coap.h"
|
||||
|
||||
|
||||
int _suit_handler(int argc, char **argv)
|
||||
|
@ -1,9 +1,5 @@
|
||||
SUBMODULES := 1
|
||||
|
||||
# don't complain about missing submodule .c file.
|
||||
# necessary to not fail for suit_v*_*.
|
||||
SUBMODULES_NOFORCE := 1
|
||||
|
||||
DIRS += v4
|
||||
ifneq (,$(filter suit_transport_%,$(USEMODULE)))
|
||||
DIRS += transport
|
||||
endif
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
|
@ -13,7 +13,7 @@
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief SUIT conditions
|
||||
* @brief SUIT condition initializers
|
||||
*
|
||||
* @author Koen Zandberg <koen@bergzand.net>
|
||||
* @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.
|
||||
*/
|
||||
/**
|
||||
* @ingroup sys_suit_v4
|
||||
* @ingroup sys_suit
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief SUIT v4 policy checking code
|
||||
* @brief SUIT update policy checking code
|
||||
*
|
||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "suit/v4/suit.h"
|
||||
#include "suit/v4/policy.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)) {
|
||||
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 "periph/pm.h"
|
||||
|
||||
#include "suit/coap.h"
|
||||
#include "suit/transport/coap.h"
|
||||
#include "net/sock/util.h"
|
||||
|
||||
#ifdef MODULE_RIOTBOOT_SLOT
|
||||
@ -40,8 +40,8 @@
|
||||
#include "riotboot/flashwrite.h"
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_SUIT_V4
|
||||
#include "suit/v4/suit.h"
|
||||
#ifdef MODULE_SUIT
|
||||
#include "suit.h"
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_PROGRESS_BAR)
|
||||
@ -53,7 +53,7 @@
|
||||
|
||||
#ifndef SUIT_COAP_STACKSIZE
|
||||
/* 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
|
||||
|
||||
#ifndef SUIT_COAP_PRIO
|
||||
@ -74,8 +74,9 @@ static char _stack[SUIT_COAP_STACKSIZE];
|
||||
static char _url[SUIT_URL_MAX];
|
||||
static uint8_t _manifest_buf[SUIT_MANIFEST_BUFSIZE];
|
||||
|
||||
#ifdef MODULE_SUIT_V4
|
||||
static inline void _print_download_progress(size_t offset, size_t len, uint32_t image_size)
|
||||
#ifdef MODULE_SUIT
|
||||
static inline void _print_download_progress(size_t offset, size_t len,
|
||||
uint32_t image_size)
|
||||
{
|
||||
(void)offset;
|
||||
(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)
|
||||
{
|
||||
int32_t left = (int32_t)(deadline - _now());
|
||||
|
||||
if (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;
|
||||
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);
|
||||
|
||||
/* 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 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) {
|
||||
if (res == -EAGAIN) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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_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_len = 0;
|
||||
@ -237,8 +246,8 @@ static int _fetch_block(coap_pkt_t *pkt, uint8_t *buf, sock_udp_t *sock, const c
|
||||
}
|
||||
|
||||
int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path,
|
||||
coap_blksize_t blksize,
|
||||
coap_blockwise_cb_t callback, void *arg)
|
||||
coap_blksize_t blksize,
|
||||
coap_blockwise_cb_t callback, void *arg)
|
||||
{
|
||||
/* mmmmh dynamically sized array */
|
||||
uint8_t buf[64 + (0x1 << (blksize + 4))];
|
||||
@ -248,14 +257,12 @@ int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path,
|
||||
/* HACK: use random local port */
|
||||
local.port = 0x8000 + (xtimer_now_usec() % 0XFFF);
|
||||
|
||||
|
||||
sock_udp_t sock;
|
||||
int res = sock_udp_create(&sock, &local, remote, 0);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
int more = 1;
|
||||
size_t num = 0;
|
||||
res = -1;
|
||||
@ -269,7 +276,8 @@ int suit_coap_get_blockwise(sock_udp_ep_t *remote, const char *path,
|
||||
coap_get_block2(&pkt, &block2);
|
||||
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");
|
||||
res = -1;
|
||||
goto out;
|
||||
@ -290,8 +298,8 @@ out:
|
||||
}
|
||||
|
||||
int suit_coap_get_blockwise_url(const char *url,
|
||||
coap_blksize_t blksize,
|
||||
coap_blockwise_cb_t callback, void *arg)
|
||||
coap_blksize_t blksize,
|
||||
coap_blockwise_cb_t callback, void *arg)
|
||||
{
|
||||
char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN];
|
||||
char urlpath[CONFIG_SOCK_URLPATH_MAXLEN];
|
||||
@ -346,25 +354,27 @@ static int _2buf(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
|
||||
}
|
||||
|
||||
ssize_t suit_coap_get_blockwise_url_buf(const char *url,
|
||||
coap_blksize_t blksize,
|
||||
uint8_t *buf, size_t len)
|
||||
coap_blksize_t blksize,
|
||||
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);
|
||||
|
||||
return (res < 0) ? (ssize_t)res : (ssize_t)_buf.offset;
|
||||
}
|
||||
|
||||
static void _suit_handle_url(const char *url)
|
||||
{
|
||||
LOG_INFO("suit_coap: downloading \"%s\"\n", url);
|
||||
ssize_t size = suit_coap_get_blockwise_url_buf(url, COAP_BLOCKSIZE_64, _manifest_buf,
|
||||
SUIT_MANIFEST_BUFSIZE);
|
||||
ssize_t size = suit_coap_get_blockwise_url_buf(url, COAP_BLOCKSIZE_64,
|
||||
_manifest_buf,
|
||||
SUIT_MANIFEST_BUFSIZE);
|
||||
if (size >= 0) {
|
||||
LOG_INFO("suit_coap: got manifest with size %u\n", (unsigned)size);
|
||||
|
||||
riotboot_flashwrite_t writer;
|
||||
#ifdef MODULE_SUIT_V4
|
||||
suit_v4_manifest_t manifest;
|
||||
#ifdef MODULE_SUIT
|
||||
suit_manifest_t manifest;
|
||||
memset(&manifest, 0, sizeof(manifest));
|
||||
|
||||
manifest.writer = &writer;
|
||||
@ -372,18 +382,18 @@ static void _suit_handle_url(const char *url)
|
||||
manifest.urlbuf_len = SUIT_URL_MAX;
|
||||
|
||||
int res;
|
||||
if ((res = suit_v4_parse(&manifest, _manifest_buf, size)) != SUIT_OK) {
|
||||
LOG_INFO("suit_v4_parse() failed. res=%i\n", res);
|
||||
if ((res = suit_parse(&manifest, _manifest_buf, size)) != SUIT_OK) {
|
||||
LOG_INFO("suit_parse() failed. res=%i\n", res);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("suit_v4_parse() success\n");
|
||||
LOG_INFO("suit_parse() success\n");
|
||||
if (!(manifest.state & SUIT_MANIFEST_HAVE_IMAGE)) {
|
||||
LOG_INFO("manifest parsed, but no image fetched\n");
|
||||
return;
|
||||
}
|
||||
|
||||
res = suit_v4_policy_check(&manifest);
|
||||
res = suit_policy_check(&manifest);
|
||||
if (res) {
|
||||
return;
|
||||
}
|
||||
@ -393,7 +403,8 @@ static void _suit_handle_url(const char *url)
|
||||
LOG_INFO("suit_coap: finalizing image flash\n");
|
||||
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);
|
||||
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 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;
|
||||
|
||||
if (offset == 0) {
|
||||
@ -428,8 +439,9 @@ int suit_flashwrite_helper(void *arg, size_t offset, uint8_t *buf, size_t len,
|
||||
}
|
||||
|
||||
if (writer->offset != offset) {
|
||||
LOG_WARNING("_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n",
|
||||
(unsigned)writer->offset, (unsigned)offset);
|
||||
LOG_WARNING(
|
||||
"_suit_flashwrite(): writer->offset=%u, offset==%u, aborting\n",
|
||||
(unsigned)writer->offset, (unsigned)offset);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -481,10 +493,11 @@ static ssize_t _version_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
|
||||
|
||||
#ifdef MODULE_RIOTBOOT_SLOT
|
||||
static ssize_t _slot_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
|
||||
void *context)
|
||||
void *context)
|
||||
{
|
||||
/* context is passed either as NULL or 0x1 for /active or /inactive */
|
||||
char c = '0';
|
||||
|
||||
if (context) {
|
||||
c += riotboot_slot_other();
|
||||
}
|
||||
@ -509,7 +522,7 @@ static ssize_t _trigger_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
|
||||
}
|
||||
else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -532,9 +545,10 @@ void suit_coap_trigger(const uint8_t *url, size_t len)
|
||||
static const coap_resource_t _subtree[] = {
|
||||
#ifdef MODULE_RIOTBOOT_SLOT
|
||||
{ "/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
|
||||
{ "/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 },
|
||||
};
|
||||
|
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
|
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…
Reference in New Issue
Block a user