imgtool: Add support for dependency description
This commit aims to add the ability to specify and add dependency TLVs to MCUBOOT. Due to the private nature of this feature, having dependency TLVs mean that the TLV Info header and these TLVs become part of the protected area (they are supposed to get signed as well). Since the TLV Info header containing the whole TLV section's size becomes protected, this size needs to be calculated in advance to get proper hash values. Change-Id: I13277a3b595acc2bb8c5084420f3d61c8d301dc2 Author: Bence Kaposzta <bence.kaposzta@arm.com> Signed-off-by: David Vincze <david.vincze@arm.com>
This commit is contained in:
parent
15aa6ef5ab
commit
da8c91993d
|
@ -1,5 +1,6 @@
|
||||||
# Copyright 2018 Nordic Semiconductor ASA
|
# Copyright 2018 Nordic Semiconductor ASA
|
||||||
# Copyright 2017 Linaro Limited
|
# Copyright 2017 Linaro Limited
|
||||||
|
# Copyright 2019 Arm Limited
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -32,6 +33,8 @@ IMAGE_HEADER_SIZE = 32
|
||||||
BIN_EXT = "bin"
|
BIN_EXT = "bin"
|
||||||
INTEL_HEX_EXT = "hex"
|
INTEL_HEX_EXT = "hex"
|
||||||
DEFAULT_MAX_SECTORS = 128
|
DEFAULT_MAX_SECTORS = 128
|
||||||
|
DEP_IMAGES_KEY = "images"
|
||||||
|
DEP_VERSIONS_KEY = "versions"
|
||||||
|
|
||||||
# Image header flags.
|
# Image header flags.
|
||||||
IMAGE_F = {
|
IMAGE_F = {
|
||||||
|
@ -48,6 +51,7 @@ TLV_VALUES = {
|
||||||
'ECDSA256': 0x22,
|
'ECDSA256': 0x22,
|
||||||
'ENCRSA2048': 0x30,
|
'ENCRSA2048': 0x30,
|
||||||
'ENCKW128': 0x31,
|
'ENCKW128': 0x31,
|
||||||
|
'DEPENDENCY': 0x40
|
||||||
}
|
}
|
||||||
|
|
||||||
TLV_INFO_SIZE = 4
|
TLV_INFO_SIZE = 4
|
||||||
|
@ -168,11 +172,49 @@ class Image():
|
||||||
len(self.payload), tsize, self.slot_size)
|
len(self.payload), tsize, self.slot_size)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
||||||
def create(self, key, enckey):
|
def create(self, key, enckey, dependencies=None):
|
||||||
self.add_header(enckey)
|
if dependencies is None:
|
||||||
|
dependencies_num = 0
|
||||||
|
protected_tlv_size = 0
|
||||||
|
else:
|
||||||
|
# Size of a Dependency TLV = Header ('BBH') + Payload('IBBHI')
|
||||||
|
# = 16 Bytes
|
||||||
|
dependencies_num = len(dependencies[DEP_IMAGES_KEY])
|
||||||
|
protected_tlv_size = (dependencies_num * 16) + TLV_INFO_SIZE
|
||||||
|
|
||||||
|
self.add_header(enckey, protected_tlv_size)
|
||||||
|
|
||||||
tlv = TLV(self.endian)
|
tlv = TLV(self.endian)
|
||||||
|
|
||||||
|
if protected_tlv_size != 0:
|
||||||
|
for i in range(dependencies_num):
|
||||||
|
e = STRUCT_ENDIAN_DICT[self.endian]
|
||||||
|
payload = struct.pack(
|
||||||
|
e + 'I'+'BBHI',
|
||||||
|
int(dependencies[DEP_IMAGES_KEY][i]),
|
||||||
|
dependencies[DEP_VERSIONS_KEY][i].major,
|
||||||
|
dependencies[DEP_VERSIONS_KEY][i].minor,
|
||||||
|
dependencies[DEP_VERSIONS_KEY][i].revision,
|
||||||
|
dependencies[DEP_VERSIONS_KEY][i].build
|
||||||
|
)
|
||||||
|
tlv.add('DEPENDENCY', payload)
|
||||||
|
# Full TLV size needs to be calculated in advance, because the
|
||||||
|
# header will be protected as well
|
||||||
|
tlv_header_size = 4
|
||||||
|
payload_digest_size = 32
|
||||||
|
keyhash_size = 32
|
||||||
|
cipherkey_size = 32
|
||||||
|
|
||||||
|
full_size = TLV_INFO_SIZE + len(tlv.buf) + tlv_header_size \
|
||||||
|
+ payload_digest_size
|
||||||
|
if key is not None:
|
||||||
|
full_size += tlv_header_size + keyhash_size \
|
||||||
|
+ tlv_header_size + key.sig_len()
|
||||||
|
if enckey is not None:
|
||||||
|
full_size += tlv_header_size + cipherkey_size
|
||||||
|
tlv_header = struct.pack(e + 'HH', TLV_INFO_MAGIC, full_size)
|
||||||
|
self.payload += tlv_header + bytes(tlv.buf)
|
||||||
|
|
||||||
# Note that ecdsa wants to do the hashing itself, which means
|
# Note that ecdsa wants to do the hashing itself, which means
|
||||||
# we get to hash it twice.
|
# we get to hash it twice.
|
||||||
sha = hashlib.sha256()
|
sha = hashlib.sha256()
|
||||||
|
@ -208,9 +250,9 @@ class Image():
|
||||||
self.payload[self.header_size:] = encryptor.update(img) + \
|
self.payload[self.header_size:] = encryptor.update(img) + \
|
||||||
encryptor.finalize()
|
encryptor.finalize()
|
||||||
|
|
||||||
self.payload += tlv.get()
|
self.payload += tlv.get()[protected_tlv_size:]
|
||||||
|
|
||||||
def add_header(self, enckey):
|
def add_header(self, enckey, protected_tlv_size):
|
||||||
"""Install the image header."""
|
"""Install the image header."""
|
||||||
|
|
||||||
flags = 0
|
flags = 0
|
||||||
|
@ -219,29 +261,29 @@ class Image():
|
||||||
|
|
||||||
e = STRUCT_ENDIAN_DICT[self.endian]
|
e = STRUCT_ENDIAN_DICT[self.endian]
|
||||||
fmt = (e +
|
fmt = (e +
|
||||||
# type ImageHdr struct {
|
# type ImageHdr struct {
|
||||||
'I' + # Magic uint32
|
'I' + # Magic uint32
|
||||||
'I' + # LoadAddr uint32
|
'I' + # LoadAddr uint32
|
||||||
'H' + # HdrSz uint16
|
'H' + # HdrSz uint16
|
||||||
'H' + # Pad1 uint16
|
'H' + # PTLVSz uint16
|
||||||
'I' + # ImgSz uint32
|
'I' + # ImgSz uint32
|
||||||
'I' + # Flags uint32
|
'I' + # Flags uint32
|
||||||
'BBHI' + # Vers ImageVersion
|
'BBHI' + # Vers ImageVersion
|
||||||
'I' # Pad2 uint32
|
'I' # Pad1 uint32
|
||||||
) # }
|
) # }
|
||||||
assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
|
assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
|
||||||
header = struct.pack(fmt,
|
header = struct.pack(fmt,
|
||||||
IMAGE_MAGIC,
|
IMAGE_MAGIC,
|
||||||
0, # LoadAddr
|
0, # LoadAddr
|
||||||
self.header_size,
|
self.header_size,
|
||||||
0, # Pad1
|
protected_tlv_size, # TLV Info header + Dependency TLVs
|
||||||
len(self.payload) - self.header_size, # ImageSz
|
len(self.payload) - self.header_size, # ImageSz
|
||||||
flags, # Flags
|
flags, # Flags
|
||||||
self.version.major,
|
self.version.major,
|
||||||
self.version.minor or 0,
|
self.version.minor or 0,
|
||||||
self.version.revision or 0,
|
self.version.revision or 0,
|
||||||
self.version.build or 0,
|
self.version.build or 0,
|
||||||
0) # Pad2
|
0) # Pad1
|
||||||
self.payload = bytearray(self.payload)
|
self.payload = bytearray(self.payload)
|
||||||
self.payload[:len(header)] = header
|
self.payload[:len(header)] = header
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright 2017 Linaro Limited
|
# Copyright 2017 Linaro Limited
|
||||||
|
# Copyright 2019 Arm Limited
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
import click
|
import click
|
||||||
import getpass
|
import getpass
|
||||||
import imgtool.keys as keys
|
import imgtool.keys as keys
|
||||||
|
@ -106,6 +108,29 @@ def validate_header_size(ctx, param, value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_dependencies(ctx, param, value):
|
||||||
|
if value is not None:
|
||||||
|
versions = []
|
||||||
|
images = re.findall(r"\((\d+)", value)
|
||||||
|
if len(images) == 0:
|
||||||
|
raise click.BadParameter(
|
||||||
|
"Image dependency format is invalid: {}".format(value))
|
||||||
|
raw_versions = re.findall(r",\s*([0-9.+]+)\)", value)
|
||||||
|
if len(images) != len(raw_versions):
|
||||||
|
raise click.BadParameter(
|
||||||
|
'''There's a mismatch between the number of dependency images
|
||||||
|
and versions in: {}'''.format(value))
|
||||||
|
for raw_version in raw_versions:
|
||||||
|
try:
|
||||||
|
versions.append(decode_version(raw_version))
|
||||||
|
except ValueError as e:
|
||||||
|
raise click.BadParameter("{}".format(e))
|
||||||
|
dependencies = dict()
|
||||||
|
dependencies[image.DEP_IMAGES_KEY] = images
|
||||||
|
dependencies[image.DEP_VERSIONS_KEY] = versions
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
class BasedIntParamType(click.ParamType):
|
class BasedIntParamType(click.ParamType):
|
||||||
name = 'integer'
|
name = 'integer'
|
||||||
|
|
||||||
|
@ -138,6 +163,9 @@ class BasedIntParamType(click.ParamType):
|
||||||
help='Add --header-size zeroed bytes at the beginning of the image')
|
help='Add --header-size zeroed bytes at the beginning of the image')
|
||||||
@click.option('-H', '--header-size', callback=validate_header_size,
|
@click.option('-H', '--header-size', callback=validate_header_size,
|
||||||
type=BasedIntParamType(), required=True)
|
type=BasedIntParamType(), required=True)
|
||||||
|
@click.option('-d', '--dependencies', callback=get_dependencies,
|
||||||
|
required=False, help='''Add dependence on another image, format:
|
||||||
|
"(<image_ID>,<image_version>), ... "''')
|
||||||
@click.option('-v', '--version', callback=validate_version, required=True)
|
@click.option('-v', '--version', callback=validate_version, required=True)
|
||||||
@click.option('--align', type=click.Choice(['1', '2', '4', '8']),
|
@click.option('--align', type=click.Choice(['1', '2', '4', '8']),
|
||||||
required=True)
|
required=True)
|
||||||
|
@ -146,7 +174,8 @@ class BasedIntParamType(click.ParamType):
|
||||||
INFILE and OUTFILE are parsed as Intel HEX if the params have
|
INFILE and OUTFILE are parsed as Intel HEX if the params have
|
||||||
.hex extension, othewise binary format is used''')
|
.hex extension, othewise binary format is used''')
|
||||||
def sign(key, align, version, header_size, pad_header, slot_size, pad,
|
def sign(key, align, version, header_size, pad_header, slot_size, pad,
|
||||||
max_sectors, overwrite_only, endian, encrypt, infile, outfile):
|
max_sectors, overwrite_only, endian, encrypt, infile, outfile,
|
||||||
|
dependencies):
|
||||||
img = image.Image(version=decode_version(version), header_size=header_size,
|
img = image.Image(version=decode_version(version), header_size=header_size,
|
||||||
pad_header=pad_header, pad=pad, align=int(align),
|
pad_header=pad_header, pad=pad, align=int(align),
|
||||||
slot_size=slot_size, max_sectors=max_sectors,
|
slot_size=slot_size, max_sectors=max_sectors,
|
||||||
|
@ -159,7 +188,7 @@ def sign(key, align, version, header_size, pad_header, slot_size, pad,
|
||||||
raise Exception("Encryption only available with RSA key")
|
raise Exception("Encryption only available with RSA key")
|
||||||
if key and not isinstance(key, keys.RSA2048):
|
if key and not isinstance(key, keys.RSA2048):
|
||||||
raise Exception("Signing only available with private RSA key")
|
raise Exception("Signing only available with private RSA key")
|
||||||
img.create(key, enckey)
|
img.create(key, enckey, dependencies)
|
||||||
img.save(outfile)
|
img.save(outfile)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue