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:
parent
1084100cf4
commit
71b8f981df
|
@ -19,6 +19,7 @@ let
|
|||
python37.pkgs.cryptography
|
||||
python37.pkgs.intelhex
|
||||
python37.pkgs.setuptools
|
||||
python37.pkgs.cbor
|
||||
]
|
||||
);
|
||||
in
|
||||
|
|
|
@ -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)
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
cryptography>=2.6
|
||||
intelhex
|
||||
click
|
||||
cbor>=1.0.0
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Reference in New Issue