imgtool: Add CBOR encoded boot record to TLV area
Add new '--boot-record' option for imgtool to add a new type of TLV to
the image manifest called BOOT_RECORD. This TLV contains CBOR encoded
data with some basic information about the image (SW component) it
belongs to, these are the following:
- SW type (role of the software component)
- SW version
- Signer ID (identifies the signing authority)
- Measurement value (hash of the image)
- Measurement type (algorithm used to calculate the measurement value)
The boot_record.py file and most of the modifications in image.py are
coming from the Trusted Firmware-M project
(https://www.trustedfirmware.org/about/).
Hash of the source commit: 08d5572b4bcee306d8cf709c2200359a22d5b72c.
This patch is based on the recommendations of Arm's Platform Security
Architecture (PSA) and its purpose is to support compliance with it.
Change-Id: I379ccc57b48ad2311837cb3fd90f5f9d1c9b5bac
Signed-off-by: David Vincze <david.vincze@linaro.org>
diff --git a/scripts/imgtool.nix b/scripts/imgtool.nix
index 6072742..7d750df 100644
--- a/scripts/imgtool.nix
+++ b/scripts/imgtool.nix
@@ -19,6 +19,7 @@
python37.pkgs.cryptography
python37.pkgs.intelhex
python37.pkgs.setuptools
+ python37.pkgs.cbor
]
);
in
diff --git a/scripts/imgtool/boot_record.py b/scripts/imgtool/boot_record.py
new file mode 100644
index 0000000..4112b22
--- /dev/null
+++ b/scripts/imgtool/boot_record.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2019, Arm Limited.
+# Copyright (c) 2020, Linaro Limited
+#
+# 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 enum import Enum
+import cbor
+
+
+class SwComponent(int, Enum):
+ """
+ Software component property IDs specified by
+ Arm's PSA Attestation API 1.0 document.
+ """
+ TYPE = 1
+ MEASUREMENT_VALUE = 2
+ VERSION = 4
+ SIGNER_ID = 5
+ MEASUREMENT_DESCRIPTION = 6
+
+
+def create_sw_component_data(sw_type, sw_version, sw_measurement_description,
+ sw_measurement_value, sw_signer_id):
+
+ # List of software component properties (Key ID + value)
+ properties = {
+ SwComponent.TYPE: sw_type,
+ SwComponent.VERSION: sw_version,
+ SwComponent.SIGNER_ID: sw_signer_id,
+ SwComponent.MEASUREMENT_DESCRIPTION: sw_measurement_description,
+ }
+
+ # Note: The measurement value must be the last item of the property
+ # list because later it will be modified by the bootloader.
+ properties[SwComponent.MEASUREMENT_VALUE] = sw_measurement_value
+
+ return cbor.dumps(properties)
diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py
index 9701e21..644a028 100644
--- a/scripts/imgtool/image.py
+++ b/scripts/imgtool/image.py
@@ -19,6 +19,7 @@
"""
from . import version as versmod
+from .boot_record import create_sw_component_data
import click
from enum import Enum
from intelhex import IntelHex
@@ -42,6 +43,7 @@
MAX_ALIGN = 8
DEP_IMAGES_KEY = "images"
DEP_VERSIONS_KEY = "versions"
+MAX_SW_TYPE_LENGTH = 12 # Bytes
# Image header flags.
IMAGE_F = {
@@ -63,6 +65,7 @@
'ENCEC256': 0x32,
'DEPENDENCY': 0x40,
'SEC_CNT': 0x50,
+ 'BOOT_RECORD': 0x60,
}
TLV_SIZE = 4
@@ -256,9 +259,18 @@
format=PublicFormat.UncompressedPoint)
return cipherkey, ciphermac, pubk
- def create(self, key, enckey, dependencies=None):
+ def create(self, key, enckey, dependencies=None, sw_type=None):
self.enckey = enckey
+ # Calculate the hash of the public key
+ if key is not None:
+ pub = key.get_public_bytes()
+ sha = hashlib.sha256()
+ sha.update(pub)
+ pubbytes = sha.digest()
+ else:
+ pubbytes = bytes(hashlib.sha256().digest_size)
+
protected_tlv_size = 0
if self.security_counter is not None:
@@ -266,6 +278,32 @@
# = 4 + 4 = 8 Bytes
protected_tlv_size += TLV_SIZE + 4
+ if sw_type is not None:
+ if len(sw_type) > MAX_SW_TYPE_LENGTH:
+ msg = "'{}' is too long ({} characters) for sw_type. Its " \
+ "maximum allowed length is 12 characters.".format(
+ sw_type, len(sw_type))
+ raise click.UsageError(msg)
+
+ image_version = (str(self.version.major) + '.'
+ + str(self.version.minor) + '.'
+ + str(self.version.revision))
+
+ # The image hash is computed over the image header, the image
+ # itself and the protected TLV area. However, the boot record TLV
+ # (which is part of the protected area) should contain this hash
+ # before it is even calculated. For this reason the script fills
+ # this field with zeros and the bootloader will insert the right
+ # value later.
+ digest = bytes(hashlib.sha256().digest_size)
+
+ # Create CBOR encoded boot record
+ boot_record = create_sw_component_data(sw_type, image_version,
+ "SHA256", digest,
+ pubbytes)
+
+ protected_tlv_size += TLV_SIZE + len(boot_record)
+
if dependencies is not None:
# Size of a Dependency TLV = Header ('HH') + Payload('IBBHI')
# = 4 + 12 = 16 Bytes
@@ -293,6 +331,9 @@
payload = struct.pack(e + 'I', self.security_counter)
prot_tlv.add('SEC_CNT', payload)
+ if sw_type is not None:
+ prot_tlv.add('BOOT_RECORD', boot_record)
+
if dependencies is not None:
for i in range(dependencies_num):
payload = struct.pack(
@@ -319,10 +360,6 @@
tlv.add('SHA256', digest)
if key is not None:
- pub = key.get_public_bytes()
- sha = hashlib.sha256()
- sha.update(pub)
- pubbytes = sha.digest()
tlv.add('KEYHASH', pubbytes)
# `sign` expects the full image payload (sha256 done internally),
diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py
index d998c5b..fa15200 100755
--- a/scripts/imgtool/main.py
+++ b/scripts/imgtool/main.py
@@ -1,6 +1,6 @@
#! /usr/bin/env python3
#
-# Copyright 2017 Linaro Limited
+# Copyright 2017-2020 Linaro Limited
# Copyright 2019-2020 Arm Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,11 @@
from imgtool.version import decode_version
from .keys import RSAUsageError, ECDSAUsageError, Ed25519UsageError
+MIN_PYTHON_VERSION = (3, 6)
+if sys.version_info < MIN_PYTHON_VERSION:
+ sys.exit("Python %s.%s or newer is required by imgtool."
+ % MIN_PYTHON_VERSION)
+
def gen_rsa2048(keyfile, passwd):
keys.RSA.generate().export_private(path=keyfile, passwd=passwd)
@@ -230,6 +235,10 @@
default='little', help="Select little or big endian")
@click.option('--overwrite-only', default=False, is_flag=True,
help='Use overwrite-only instead of swap upgrades')
+@click.option('--boot-record', metavar='sw_type', help='Create CBOR encoded '
+ 'boot record TLV. The sw_type represents the role of the '
+ 'software component (e.g. CoFM for coprocessor firmware). '
+ '[max. 12 characters]')
@click.option('-M', '--max-sectors', type=int,
help='When padding allow for this amount of sectors (defaults '
'to 128)')
@@ -263,7 +272,7 @@
def sign(key, align, version, pad_sig, header_size, pad_header, slot_size, pad, confirm,
max_sectors, overwrite_only, endian, encrypt, infile, outfile,
dependencies, load_addr, hex_addr, erased_val, save_enctlv,
- security_counter):
+ security_counter, boot_record):
img = image.Image(version=decode_version(version), header_size=header_size,
pad_header=pad_header, pad=pad, confirm=confirm,
align=int(align), slot_size=slot_size,
@@ -286,7 +295,7 @@
if pad_sig and hasattr(key, 'pad_sig'):
key.pad_sig = True
- img.create(key, enckey, dependencies)
+ img.create(key, enckey, dependencies, boot_record)
img.save(outfile, hex_addr)
diff --git a/scripts/requirements.txt b/scripts/requirements.txt
index d3bcfbb..9481e2c 100644
--- a/scripts/requirements.txt
+++ b/scripts/requirements.txt
@@ -1,3 +1,4 @@
cryptography>=2.6
intelhex
click
+cbor>=1.0.0
diff --git a/scripts/setup.py b/scripts/setup.py
index 2789094..058d0cb 100644
--- a/scripts/setup.py
+++ b/scripts/setup.py
@@ -10,10 +10,12 @@
license="Apache Software License",
url="http://github.com/JuulLabs-OSS/mcuboot",
packages=setuptools.find_packages(),
+ python_requires='>=3.6',
install_requires=[
'cryptography>=2.4.2',
'intelhex>=2.2.1',
'click',
+ 'cbor>=1.0.0',
],
entry_points={
"console_scripts": ["imgtool=imgtool.main:imgtool"]