tools/manifest_tools/pfm_generator_v1.py (266 lines of code) (raw):

""" Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. """ from __future__ import print_function from __future__ import unicode_literals import os import ctypes import binascii import argparse import manifest_types import manifest_common from Crypto.PublicKey import RSA PFM_CONFIG_FILENAME = "pfm_generator.config" VALIDATE_ON_BOOT_FLAG = 1 # Table which indexes public keys used in this PFM file pbkey_table = [] # List of all FW version strings version_list = [] # These ctype structs resemble the format the PFM consumer utilizes class pfm_fw_header (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('length', ctypes.c_ushort), ('version_length', ctypes.c_ubyte), ('blank_byte', ctypes.c_ubyte), ('version_addr', ctypes.c_uint), ('img_count', ctypes.c_ubyte), ('rw_count', ctypes.c_ubyte), ('reserved', ctypes.c_ushort)] class pfm_allowable_fw_header (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('length', ctypes.c_ushort), ('fw_count', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte)] class pfm_public_key_header (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('length', ctypes.c_ushort), ('key_length', ctypes.c_ushort), ('key_exponent', ctypes.c_uint), ('id', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte * 3)] class pfm_key_manifest_header (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('length', ctypes.c_ushort), ('key_count', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte)] class pfm_flash_region (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('start_addr', ctypes.c_uint), ('end_addr', ctypes.c_uint)] class pfm_image_header (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('length', ctypes.c_ushort), ('flags', ctypes.c_ushort), ('key_id', ctypes.c_ubyte), ('region_count', ctypes.c_ubyte), ('sig_length', ctypes.c_ushort)] def process_pbkey (xml_list): """ Iterate through all public keys used in this PFM and create a public key table. :param xml_list: List of parsed XMLs for different FW to be included in PFM """ for _, xml in xml_list.items (): if "signed_imgs" in xml: for img in xml["signed_imgs"]: if "pbkey" in img: pbkey = RSA.importKey(img["pbkey"]) img.pop("pbkey") if pbkey not in pbkey_table: pbkey_table.append (pbkey) img["pbkey_index"] = len (pbkey_table) - 1 else: img["pbkey_index"] = pbkey_table.index(pbkey) def generate_pfm (pfm_header_instance, allowable_fw_header_instance, allowable_fw_list, keys_header_instance, keys_list, platform_header_instance): """ Create a PFM object from all the different PFM components :param pfm_header_instance: Instance of a PFM header :param allowable_fw_header_instance: Instance of a PFM allowable FW header :param allowable_fw_list: List of allowable FWs to be included in PFM :param keys_header_instance: Instance of a PFM key manifest header :param keys_list: List of public keys to be included in PFM :param platform_header_instance: Instance of a PFM platform header :return Instance of a PFM object """ fw_size = allowable_fw_header_instance.length - ctypes.sizeof (allowable_fw_header_instance) keys_size = keys_header_instance.length - ctypes.sizeof (keys_header_instance) platform_size = ctypes.sizeof (platform_header_instance) class pfm (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('manifest_header', manifest_common.manifest_header), ('allowable_fw_header', pfm_allowable_fw_header), ('allowable_fw', ctypes.c_ubyte * fw_size), ('key_manifest_header', pfm_key_manifest_header), ('pb_keys', ctypes.c_ubyte * keys_size), ('platform', ctypes.c_ubyte * platform_size)] fw_buf = (ctypes.c_ubyte * fw_size)() fw_buf_len = manifest_common.move_list_to_buffer (fw_buf, 0, allowable_fw_list) keys_buf = (ctypes.c_ubyte * keys_size)() keys_buf_len = manifest_common.move_list_to_buffer (keys_buf, 0, keys_list) platform_buf = (ctypes.c_ubyte * platform_size)() ctypes.memmove (ctypes.addressof (platform_buf), ctypes.addressof (platform_header_instance), platform_size) return pfm (pfm_header_instance, allowable_fw_header_instance, fw_buf, keys_header_instance, keys_buf, platform_buf) def generate_flash_region (filename, region_list): """ Create a list of flash region struct instances from region list :param filename: XML filename :param region_list: List of flash regions :return List of flash region struct instances """ flash_list = [] for region in region_list: if "start" in region and "end" in region: start_addr = int (region["start"], 16) end_addr = int (region["end"], 16) if end_addr <= start_addr: raise ValueError ("Failed to generate PFM: Image has an invalid flash region - {0}" .format (filename)) flash_list.append (pfm_flash_region(start_addr, end_addr)) return flash_list def generate_img_instance (filename, img, regions, signature): """ Create a list of signed image instances :param filename: Parsed XML file :param img: Signed or hashed image for which an image instance to generated :param regions: List of flash regions :param signature: Buffer containing either signature or hash of the image :return List of signed image instances """ if "validate" not in img: raise KeyError ("Failed to generate PFM: Image has no validate flag - {0}".format ( filename)) if "pbkey_index" not in img: raise KeyError ("Failed to generate PFM: Image has no public key index - {0}".format ( filename)) flags = 1 if img["validate"] == "true" else 0 header = pfm_image_header(0, flags, img["pbkey_index"], len (regions), len (signature)) sig_arr = (ctypes.c_ubyte * len (signature)).from_buffer_copy (signature) region_arr = (pfm_flash_region * len (regions))(*regions) class pfm_signed_img (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('header', pfm_image_header), ('img_signature', ctypes.c_ubyte * len (signature)), ('flash_regions', pfm_flash_region * len (regions))] return pfm_signed_img (header, sig_arr, region_arr) def generate_signed_image (filename, img_list): """ Create a list of signed image struct instances from image list :param filename: parsed XML file :param img_list: List of allowable firmware images :return List of signed image struct instances """ signed_list = [] for img in img_list: if "regions" not in img: raise KeyError ("Failed to generate PFM: Image has no regions list - {0}".format ( filename)) if "signature" not in img: raise KeyError ("Failed to generate PFM: Image has no signature - {0}".format ( filename)) regions = generate_flash_region(filename, img["regions"]) img_instance = generate_img_instance(filename, img, regions, img["signature"]) img_instance.header.length = ctypes.sizeof (img_instance) signed_list.append (img_instance) return signed_list def generate_image_and_rw_region_list (filename, xml, version_addr, version_length): """ Create a list of signed images and RW regions from parsed XML :param filename: parsed XML filename :param xml: Parsed XML :param version_addr: Address of the version string :param version_length: Length of the version string :return signed images list, rw regions list, rw regions array """ signed_imgs_list = [] rw_regions_list = [] rw_regions_arr = None all_regions = [] rw_regions_list = generate_flash_region (filename, xml["rw_regions"]) rw_regions_arr = (pfm_flash_region * len (rw_regions_list)) (*rw_regions_list) signed_imgs_list = generate_signed_image (filename, xml["signed_imgs"]) for region in rw_regions_list: manifest_common.check_region_address_validity (region.start_addr, region.end_addr) all_regions.append ([region.start_addr, region.end_addr]) flags = 0 for img in signed_imgs_list: flags |= (img.header.flags & VALIDATE_ON_BOOT_FLAG) for region in img.flash_regions: all_regions.append ([region.start_addr, region.end_addr]) if (img.header.flags & VALIDATE_ON_BOOT_FLAG) == VALIDATE_ON_BOOT_FLAG: if ((version_addr + version_length - 1) <= region.end_addr and version_addr >= region.start_addr): version_addr_valid = True if not version_addr_valid: raise ValueError ("Failed to generate PFM: Version address not in a signed image with validate on boot flag set - {0}".format ( filename)) if flags == 0: raise ValueError ("Failed to generate PFM: XML has no signed images with validate on boot flag set - {0}".format ( filename)) all_regions.sort () for i_region, region in enumerate (all_regions): for i_comp in range (i_region + 1, len (all_regions)): if region[1] >= all_regions[i_comp][0]: raise ValueError ("Failed to generate PFM: XML has overlapping regions - {0}".format ( filename)) return rw_regions_arr, len (rw_regions_list), signed_imgs_list def generate_allowable_fw_list (xml_list): """ Create a list of allowable firmware from parsed XML list :param xml_list: List of parsed XML of firmware to be included in PFM :return list of allowable firmware struct instances """ fw_list = [] for filename, xml in xml_list.items (): version_addr = int (xml["version_addr"], 16) unused_byte = int (xml["unused_byte"], 16) version_addr_valid = False for version in version_list: if version == xml["version_id"]: raise KeyError ("Failed to generate PFM: Duplicate version ID - {0}".format ( xml["version_id"])) elif version.startswith (xml["version_id"]) or xml["version_id"].startswith (version): raise ValueError ("Failed to generate PFM: Ambiguous version ID - {0}, {1}".format ( xml["version_id"], version)) version_list.append (xml["version_id"]) manifest_common.check_maximum (unused_byte, 255, "Unused byte") manifest_common.check_maximum (len (xml["version_id"]), 255, "Version ID {0} string length".format (xml["version_id"])) header = pfm_fw_header (0, len (xml["version_id"]), unused_byte, version_addr, len (xml["signed_imgs"]), len (xml["rw_regions"]), 0) rw_regions_buf, rw_regions_size, signed_imgs_list = generate_image_and_rw_region_list ( filename, xml, version_addr, header.version_length) signed_imgs_size = 0 for img in signed_imgs_list: signed_imgs_size = signed_imgs_size + ctypes.sizeof (img) signed_imgs_buf = (ctypes.c_ubyte * signed_imgs_size)() signed_imgs_buf_len = manifest_common.move_list_to_buffer (signed_imgs_buf, 0, signed_imgs_list) manifest_common.check_maximum (len (xml["version_id"]), 255, "Version ID {0} length".format (xml["version_id"])) padding, padding_len = manifest_common.generate_4byte_padding_buf (len (xml["version_id"])) class pfm_allowable_fw(ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('header', pfm_fw_header), ('version_id', ctypes.c_char * len (xml["version_id"])), ('padding', ctypes.c_ubyte * padding_len), ('rw_regions', pfm_flash_region * rw_regions_size), ('signed_imgs', ctypes.c_ubyte * signed_imgs_size)] fw = pfm_allowable_fw(header, xml["version_id"].encode ('utf-8'), padding, rw_regions_buf, signed_imgs_buf) fw.header.length = ctypes.sizeof (pfm_allowable_fw) fw_list.append (fw) return fw_list def generate_allowable_fw_header (fw_list): """ Create an allowable FW header from an allowable FW list :param fw_list: List of allowable FW to be included in PFM :return Allowable FW header instance """ size = ctypes.sizeof (pfm_allowable_fw_header) for fw in fw_list: size = size + ctypes.sizeof (fw) return pfm_allowable_fw_header (size, len (fw_list), 0) def generate_key_instance (header, modulus): """ Create a key instance from header and modulus :param header: PFM public key header :param modulus: List of public key modulus digits :return Public key instance """ arr = (ctypes.c_ubyte * len (modulus)).from_buffer_copy (modulus) class pfm_public_key (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('header', pfm_public_key_header), ('public_key_modulus', ctypes.c_ubyte * len (modulus))] return pfm_public_key (header, arr) def generate_pbkey_list (): """ Create a public key list from the public key table :return List of public key instances """ keys_list = [] for pub_key in pbkey_table: reserved_buf = (ctypes.c_ubyte * 3)() ctypes.memset (reserved_buf, 0, 3) mod_fmt = "%%0%dx" % (pub_key.n.bit_length() // 4) modulus = binascii.a2b_hex (mod_fmt % pub_key.n) header = pfm_public_key_header(0, len (modulus), pub_key.e, pbkey_table.index (pub_key), reserved_buf) key = generate_key_instance (header, modulus) key.header.length = ctypes.sizeof (key) keys_list.append (key) return keys_list def generate_pbkey_header (keys_list): """ Create a public key manifest header from list of public key objects :param keys_list: List of public key objects :return Public key manifest header """ size = ctypes.sizeof (pfm_key_manifest_header) for key in keys_list: size = size + ctypes.sizeof (key) return pfm_key_manifest_header (size, len (keys_list), 0) def generate_platform_info (platform_id): """ Create the platform information section of the manifest. :param platform_id: Platform ID string :return Platform manifest section """ manifest_common.check_maximum (len (platform_id), 255, "Platform ID {0} length".format ( platform_id)) padding, padding_len = manifest_common.generate_4byte_padding_buf (len (platform_id)) class manifest_platform_id (ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [('length', ctypes.c_ushort), ('id_length', ctypes.c_ubyte), ('reserved', ctypes.c_ubyte), ('platform_id', ctypes.c_char * len (platform_id)), ('padding', ctypes.c_ubyte * padding_len)] return manifest_platform_id (ctypes.sizeof (manifest_platform_id), len (platform_id), 0, platform_id.encode ('utf-8'), padding) def generate_v1_pfm (pfm_id, key_size, hash_type, key_type, processed_xml, bypass, sign, key, output): """ Generate v1 PFM manifest from processed XML :param pfm_id: PFM id :param key_size: Size of signing key, optional :param hash_type: Hashing algorithm :param key_type: Signing key algorithm, optional :param processed_xml: List of parsed XML files to process for PFM :param bypass: Boolean indicating whether to generate bypass PFM or not :param sign: Boolean indicating whether to sign manifest or not :param key: Key to use for signing :param output: Output filename """ manifest_header = manifest_common.generate_manifest_header (pfm_id, key_size, manifest_types.PFM, hash_type, key_type, manifest_types.VERSION_1) manifest_header_len = ctypes.sizeof (manifest_header) process_pbkey (processed_xml) if (bypass): allowable_fw_list = [] keys_list = [] else: allowable_fw_list = generate_allowable_fw_list (processed_xml) keys_list = generate_pbkey_list () allowable_fw_header = generate_allowable_fw_header (allowable_fw_list) keys_header = generate_pbkey_header (keys_list) platform_id = manifest_common.get_platform_id_from_xml_list (processed_xml) platform_header = generate_platform_info (platform_id) manifest_header.length = ctypes.sizeof (manifest_header) + keys_header.length + \ allowable_fw_header.length + manifest_header.sig_length + ctypes.sizeof (platform_header) pfm = generate_pfm (manifest_header, allowable_fw_header, allowable_fw_list, keys_header, keys_list, platform_header) manifest_common.write_manifest (manifest_types.VERSION_1, sign, pfm, key, key_size, key_type, output, manifest_header.length - manifest_header.sig_length, manifest_header.sig_length)