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>
This commit is contained in:
David Vincze 2020-03-17 19:08:12 +01:00 committed by Dávid Vincze
parent 1084100cf4
commit 71b8f981df
6 changed files with 105 additions and 8 deletions

View File

@ -19,6 +19,7 @@ let
python37.pkgs.cryptography
python37.pkgs.intelhex
python37.pkgs.setuptools
python37.pkgs.cbor
]
);
in

View File

@ -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)

View File

@ -19,6 +19,7 @@ Image signing and management.
"""
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 @@ DEFAULT_MAX_SECTORS = 128
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 @@ TLV_VALUES = {
'ENCEC256': 0x32,
'DEPENDENCY': 0x40,
'SEC_CNT': 0x50,
'BOOT_RECORD': 0x60,
}
TLV_SIZE = 4
@ -256,9 +259,18 @@ class Image():
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 @@ class Image():
# = 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 @@ class Image():
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 @@ class Image():
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),

View File

@ -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 import image, imgtool_version
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 @@ class BasedIntParamType(click.ParamType):
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 @@ class BasedIntParamType(click.ParamType):
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 @@ def sign(key, align, version, pad_sig, header_size, pad_header, slot_size, pad,
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)

View File

@ -1,3 +1,4 @@
cryptography>=2.6
intelhex
click
cbor>=1.0.0

View File

@ -10,10 +10,12 @@ setuptools.setup(
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"]