diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index 3ccd86f9..c36f802a 100644 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -1,5 +1,6 @@ # Copyright 2018 Nordic Semiconductor ASA # Copyright 2017 Linaro Limited +# Copyright 2019 Arm Limited # # Licensed under the Apache License, Version 2.0 (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" INTEL_HEX_EXT = "hex" DEFAULT_MAX_SECTORS = 128 +DEP_IMAGES_KEY = "images" +DEP_VERSIONS_KEY = "versions" # Image header flags. IMAGE_F = { @@ -48,6 +51,7 @@ TLV_VALUES = { 'ECDSA256': 0x22, 'ENCRSA2048': 0x30, 'ENCKW128': 0x31, + 'DEPENDENCY': 0x40 } TLV_INFO_SIZE = 4 @@ -168,11 +172,49 @@ class Image(): len(self.payload), tsize, self.slot_size) raise Exception(msg) - def create(self, key, enckey): - self.add_header(enckey) + def create(self, key, enckey, dependencies=None): + 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) + 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 # we get to hash it twice. sha = hashlib.sha256() @@ -208,9 +250,9 @@ class Image(): self.payload[self.header_size:] = encryptor.update(img) + \ 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.""" flags = 0 @@ -219,29 +261,29 @@ class Image(): e = STRUCT_ENDIAN_DICT[self.endian] fmt = (e + - # type ImageHdr struct { - 'I' + # Magic uint32 - 'I' + # LoadAddr uint32 - 'H' + # HdrSz uint16 - 'H' + # Pad1 uint16 - 'I' + # ImgSz uint32 - 'I' + # Flags uint32 - 'BBHI' + # Vers ImageVersion - 'I' # Pad2 uint32 - ) # } + # type ImageHdr struct { + 'I' + # Magic uint32 + 'I' + # LoadAddr uint32 + 'H' + # HdrSz uint16 + 'H' + # PTLVSz uint16 + 'I' + # ImgSz uint32 + 'I' + # Flags uint32 + 'BBHI' + # Vers ImageVersion + 'I' # Pad1 uint32 + ) # } assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE header = struct.pack(fmt, IMAGE_MAGIC, 0, # LoadAddr self.header_size, - 0, # Pad1 + protected_tlv_size, # TLV Info header + Dependency TLVs len(self.payload) - self.header_size, # ImageSz flags, # Flags self.version.major, self.version.minor or 0, self.version.revision or 0, self.version.build or 0, - 0) # Pad2 + 0) # Pad1 self.payload = bytearray(self.payload) self.payload[:len(header)] = header diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index 96f43868..f26592d9 100755 --- a/scripts/imgtool/main.py +++ b/scripts/imgtool/main.py @@ -1,6 +1,7 @@ #! /usr/bin/env python3 # # Copyright 2017 Linaro Limited +# Copyright 2019 Arm Limited # # Licensed under the Apache License, Version 2.0 (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 # limitations under the License. +import re import click import getpass import imgtool.keys as keys @@ -106,6 +108,29 @@ def validate_header_size(ctx, param, 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): name = 'integer' @@ -138,6 +163,9 @@ class BasedIntParamType(click.ParamType): help='Add --header-size zeroed bytes at the beginning of the image') @click.option('-H', '--header-size', callback=validate_header_size, type=BasedIntParamType(), required=True) +@click.option('-d', '--dependencies', callback=get_dependencies, + required=False, help='''Add dependence on another image, format: + "(,), ... "''') @click.option('-v', '--version', callback=validate_version, required=True) @click.option('--align', type=click.Choice(['1', '2', '4', '8']), required=True) @@ -146,7 +174,8 @@ class BasedIntParamType(click.ParamType): INFILE and OUTFILE are parsed as Intel HEX if the params have .hex extension, othewise binary format is used''') 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, pad_header=pad_header, pad=pad, align=int(align), 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") if key and not isinstance(key, keys.RSA2048): raise Exception("Signing only available with private RSA key") - img.create(key, enckey) + img.create(key, enckey, dependencies) img.save(outfile)