dist/tools/suit_v3: Add ietf-v3 manifest generator
Co-authored-by: Kaspar Schleiser <kaspar@schleiser.de>
This commit is contained in:
parent
ef8daaf7be
commit
009a317b14
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
|
||||||
Loading…
x
Reference in New Issue
Block a user