[U-Boot] [PATCH 4/5] tools: add secure_boot_helper.py

Anatolij Gustschin agust at denx.de
Thu May 11 15:14:55 UTC 2017


From: Markus Valentin <mv at denx.de>

This script should be used for simple creation of secure bootable
images for baytrail platforms

Signed-off-by: Markus Valentin <mv at denx.de>
---
 tools/secure_boot_helper.py | 313 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 313 insertions(+)
 create mode 100644 tools/secure_boot_helper.py

diff --git a/tools/secure_boot_helper.py b/tools/secure_boot_helper.py
new file mode 100644
index 0000000..884786e
--- /dev/null
+++ b/tools/secure_boot_helper.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python3
+"""
+ Copyright (C) 2017 Markus Valentin <mv at denx.de>
+
+ SPDX-License-Identifier:     GPL-2.0+
+ """
+
+
+import argparse
+import binascii
+
+from hashlib import sha256
+from os.path import basename, isfile, splitext
+from os.path import join as pjoin
+from struct import pack
+
+import OpenSSL
+from OpenSSL import crypto
+
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+
+
+FSP_FILE_NAME = "fsp-sb.bin"
+FSP_STAGE2_FILE_NAME = "fsp_stage2.bin"
+U_BOOT_ROM_FILE_NAME = 'u-boot.rom'
+U_BOOT_TO_SIGN_FILE_NAME = 'u-boot-to-sign.bin'
+IBB_FILE_NAME = 'ibb.bin'
+FPF_CONFIG_FILE_NAME = 'fpf_config.txt'
+SIGNED_MANIFEST_FILE_NAME = 'signed_manifest.bin'
+UNSIGNED_MANIFEST_FILE_NAME = 'un'+SIGNED_MANIFEST_FILE_NAME
+OEM_FILE_NAME = 'oemdata.bin'
+
+OEM_PRIV_KEY_FILE_NAME = 'oemkey.pem'
+OEM_PUB_KEY_FILE_NAME = 'pub_oemkey.pem'
+OEM_PUBKEY_BIN_FILE_NAME = 'pub_oemkey.bin'
+OEM_PUBKEY_AND_SIG_FILE_NAME = 'oem_pub_sig.bin'
+
+FIT_PUB_KEY_FILE_NAME = "dev.crt"
+
+FSP_STAGE_2_SIZE = 0x1f400
+IBB_SIZE = 0x1fc00
+MANIFEST_SIZE = 0x400
+OEM_BLOCK_MAX_SIZE = 0x190
+U_BOOT_ROM_SIZE = 0x800000
+ROMFILE_SYS_TEXT_BASE = 0x00700000
+U_BOOT_TO_SIGN_OFFSET = 0x2CA0
+
+
+MANIFEST_IDENTIFIER = b'$VBM'
+VERSION = 1
+SECURE_VERSION_NUMBER = 2
+OEM_DATA_PREAMBLE = '01000200'
+
+oem_data_hash_files = []
+
+
+def append_binary_files(first_file, second_file, new_file):
+    with open(new_file, 'wb') as f:
+        f.write(bytearray(open(first_file, 'rb').read()))
+        f.write(bytearray(open(second_file, 'rb').read()))
+
+
+# this function creates a oemdata data block which gets constructed
+# as stated in section 3.2 of the document "Secure-Boot for Intel
+# Bay Trail based platfomrs with U-Boot"
+def assemble_oem_data(file_path):
+    file_size = 0
+    with open(file_path, 'wb') as f:
+        f.write(binascii.unhexlify(OEM_DATA_PREAMBLE))
+        file_size += 4
+        for hash_file in oem_data_hash_files:
+            f.write(open(hash_file, 'rb').read())
+            file_size += 32
+        pad_file_with_zeros(f, OEM_BLOCK_MAX_SIZE-file_size)
+
+
+# this function creates the final u-boot-verified.rom from
+# the original u-boot.rom and the signed Initial Boot Block
+# which contains the secure boot manifest
+def assemble_secure_boot_image(u_boot_rom, signed_ibb):
+    data = bytearray(open(u_boot_rom, 'rb').read())
+    ibb = bytearray(open(signed_ibb, 'rb').read())
+    data[-(MANIFEST_SIZE+IBB_SIZE):] = ibb
+    open("u-boot-verified.rom", 'wb').write(data)
+
+
+# when calling this function it constructs a complete secure-boot manifest
+# which is just missing oem-publickey and the manifest-signature (see
+# section 3.1)
+def create_unsigned_secure_boot_manifest(unsigned_manifest,
+                                         oem_file='oemdata.bin',
+                                         ibb='ibb.bin'):
+    with open(unsigned_manifest, 'wb') as f:
+        f.write(MANIFEST_IDENTIFIER)
+        f.write(pack('i', VERSION))
+        f.write(pack('i', MANIFEST_SIZE))
+        f.write(pack('i', SECURE_VERSION_NUMBER))
+        pad_file_with_zeros(f, 4)
+        hash_function = sha256()
+        hash_function.update(bytearray(open(ibb, 'rb').read()))
+        f.write(hash_function.digest()[::-1])
+        pad_file_with_zeros(f, 36)
+        f.write(bytearray(open(oem_file, 'rb').read()))
+        pad_file_with_zeros(f, 20)
+
+
+# fetch a subpart of a binary from byte to byte and write this part to a
+# secondary file
+def extract_binary_part(binary_to_extract_from, to_file, from_byte, to_byte):
+    data = open(binary_to_extract_from, 'rb').read()
+    open(to_file, 'wb').write(data[from_byte:to_byte])
+
+
+# calculate a sha256 checksum over a file and write a file with it to a
+# file next to the original file, if given change endianness (sometimes needed
+# because the txe engine wants a other byteorder)
+def sha256_to_file(binary_dir, file_to_hash, change_endianess=False):
+    # we collect the hashes in a list(in the correct order) to be able
+    # to put them later to the oem section
+    if not oem_data_hash_files.__contains__(hashfile_path(binary_dir,
+                                            file_to_hash)):
+        oem_data_hash_files.append(hashfile_path(binary_dir, file_to_hash))
+    with open(file_to_hash, 'rb') as f:
+        hash_function = sha256()
+        hash_function.update(f.read())
+        # write as little to file
+        if change_endianess:
+            open(hashfile_path(binary_dir, file_to_hash),
+                 'wb').write(hash_function.digest())
+        else:
+            open(hashfile_path(binary_dir, file_to_hash),
+                 'wb').write(hash_function.digest()[::-1])
+
+
+# create hashfile name using the file-to-hash name
+def hashfile_path(binary_dir, file_to_hash):
+    hash_file_name = splitext(
+                basename(file_to_hash))[0].__add__('.sha256')
+    return pjoin(binary_dir, hash_file_name)
+
+
+# pad the given files with a given byte number of zeros
+# byte count must be dividable by 4
+def pad_file_with_zeros(file_handle, byte_count):
+    if byte_count % 4 != 0:
+        print("Given  byte count is not 4-divideable exiting")
+        exit()
+    pad_count = 0
+    while pad_count < byte_count:
+        file_handle.write(pack('i', 0))
+        pad_count += 4
+
+
+# extract the modulus of a public key the txe-engine gets the publickey
+# split in modulus and exponent (for this reason we need to extract it)
+def get_modulus_from_pubkey(public_key_path):
+    public_key = open(public_key_path, 'rb').read()
+    cert = x509.load_pem_x509_certificate(public_key, default_backend())
+    return ("%X" % (cert.public_key().public_numbers().n))
+
+
+# save a given modulus and exponent to a file as binary for use within
+# the manifest
+def save_binary_public_key(pub_key_file_path, modulus, exponent=0x10001):
+    with open(pub_key_file_path, 'wb') as f:
+        f.write(binascii.unhexlify(modulus)[::-1])
+        f.write(pack('i', exponent))
+
+
+# replace the public key hash in the fuse configuration text file
+# and set the lock bit
+def replace_oem_pubkey_hash(pubkey_hash, fpf_config_path, lock_fuses):
+    data = binascii.hexlify(pubkey_hash)
+
+    new_line_hash = "FUSE_FILE_OEM_KEY_HASH_1:{:s}:{}\n"\
+                    .format(data.upper().decode('ascii'),
+                            str(lock_fuses).upper())
+    new_line_sb_enabled = "FUSE_FILE_SECURE_BOOT_EN:01:{}\n"\
+                          .format(str(lock_fuses).upper())
+
+    with open(fpf_config_path, 'w') as f:
+        f.write(new_line_sb_enabled)
+        f.write(new_line_hash)
+
+
+# for the txe engine one needs to change the endianness
+def reverse_endianess(file_to_reverse):
+    data = open(file_to_reverse, 'rb').read()
+    open(file_to_reverse, 'wb').write(data[::-1])
+
+
+# sign the given file with the given private key and
+# write it to the signature_file using openssl
+def sign_file(unsigned_file, private_key, signature_file):
+    key = open(private_key, 'r').read()
+    pkey_obj = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
+    data = open(unsigned_file, 'rb').read()
+    signature = OpenSSL.crypto.sign(pkey_obj, data, "sha256")
+    open(signature_file, 'wb').write(signature)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="This script assembles a " +
+                                     "verified boot enabled u-boot using " +
+                                     "openssl")
+    parser.add_argument("-c", "--fpf-config", default="./fpf_config.txt",
+                        help="Path to the fpf-config file defaults to" +
+                        " ./fpf_config.txt",
+                        required=True)
+    parser.add_argument("-I", "--board-dir", help="set directory to get the " +
+                        " fsp and other board related files from.")
+    parser.add_argument("-b", "--binary_dir", default=".",
+                        help="directory to fetch binaries from and " +
+                        "save created binaries to.")
+    parser.add_argument("-k", "--key-dir", default="./mykeys",
+                        help="directory to fetch keys from")
+    parser.add_argument('--lock-fuses', action='store_true',
+                        help="Set this flag to configure fuses to " +
+                        "lock the values")
+
+    args = parser.parse_args()
+
+    # assemble correct paths
+    fsp = pjoin(args.board_dir, FSP_FILE_NAME)
+    fsp_stage2 = pjoin(args.board_dir, FSP_STAGE2_FILE_NAME)
+
+    fit_public_key = pjoin(args.key_dir, FIT_PUB_KEY_FILE_NAME)
+    fit_public_key_modulus = pjoin(args.key_dir, FIT_PUB_KEY_FILE_NAME+".mod")
+
+    u_boot_rom = pjoin(args.binary_dir, U_BOOT_ROM_FILE_NAME)
+    u_boot_to_sign = pjoin(args.binary_dir, U_BOOT_TO_SIGN_FILE_NAME)
+
+    ibb = pjoin(args.binary_dir, IBB_FILE_NAME)
+
+    signed_ibb = pjoin(args.binary_dir, "signed_"+IBB_FILE_NAME)
+    unsigned_manifest = pjoin(args.binary_dir, UNSIGNED_MANIFEST_FILE_NAME)
+    signed_manifest = pjoin(args.binary_dir, SIGNED_MANIFEST_FILE_NAME)
+
+    manifest_signature = pjoin(args.binary_dir, splitext(
+                               basename(UNSIGNED_MANIFEST_FILE_NAME))[0].
+                               __add__(".signature"))
+
+    oem_file = pjoin(args.binary_dir, OEM_FILE_NAME)
+    oem_private_key = pjoin(args.key_dir, OEM_PRIV_KEY_FILE_NAME)
+    oem_public_key = pjoin(args.key_dir, OEM_PUB_KEY_FILE_NAME)
+    oem_pubkey_binary = pjoin(args.key_dir, OEM_PUBKEY_BIN_FILE_NAME)
+    oem_pubkey_and_sig = pjoin(args.key_dir,
+                               OEM_PUBKEY_AND_SIG_FILE_NAME)
+
+    # check for needed files to be available
+    for f in [fsp, u_boot_rom, fit_public_key, oem_private_key]:
+        if not isfile(f):
+            print("%s not found ... exiting" % (f))
+            exit()
+
+    # get everything from rom-file execept IBB+Manfifest(128k) and write it to
+    # seperated file and calculate hash
+    extract_binary_part(u_boot_rom, u_boot_to_sign,
+                        (ROMFILE_SYS_TEXT_BASE+U_BOOT_TO_SIGN_OFFSET),
+                        (U_BOOT_ROM_SIZE-(IBB_SIZE+MANIFEST_SIZE)))
+    sha256_to_file(args.binary_dir, u_boot_to_sign, True)
+
+    # extract stage2 of the fsp and calculate a hash about
+    # the file
+    extract_binary_part(fsp, fsp_stage2, 0, FSP_STAGE_2_SIZE)
+    sha256_to_file(args.binary_dir, fsp_stage2)
+
+    with open(fit_public_key_modulus, 'wb') as f:
+        f.write(binascii.unhexlify(get_modulus_from_pubkey(fit_public_key)))
+    sha256_to_file(args.binary_dir, fit_public_key_modulus, True)
+
+    # assemble oemdata
+    print("Assembling oem data from %d hashes: \n %s" %
+          (oem_data_hash_files.__len__(), oem_data_hash_files))
+    assemble_oem_data(oem_file)
+
+    print("Extracting last 127K:\n from %s as %s"
+          % (u_boot_rom, ibb))
+    extract_binary_part(u_boot_rom, ibb,
+                        (U_BOOT_ROM_SIZE-IBB_SIZE), U_BOOT_ROM_SIZE)
+
+    print("Creating Secure Boot Manifest")
+    create_unsigned_secure_boot_manifest(unsigned_manifest,
+                                         oem_file,
+                                         ibb)
+
+    print("Signing manifest with openssl and private key %s"
+          % (oem_private_key))
+    sign_file(unsigned_manifest, oem_private_key, manifest_signature)
+
+    print("Append public key and signature to unsigned Manifest")
+    oem_pub_key_modulus = get_modulus_from_pubkey(oem_public_key)
+    save_binary_public_key(oem_pubkey_binary, oem_pub_key_modulus)
+
+    reverse_endianess(manifest_signature)
+    append_binary_files(oem_pubkey_binary, manifest_signature,
+                        oem_pubkey_and_sig)
+
+    append_binary_files(unsigned_manifest, oem_pubkey_and_sig,
+                        signed_manifest)
+
+    hash_function = sha256()
+    hash_function.update(bytearray(open(oem_pubkey_binary, 'rb').read()))
+    replace_oem_pubkey_hash(hash_function.digest()[::-1], args.fpf_config,
+                            args.lock_fuses)
+
+    print("Append manifest with signature to ibb")
+    append_binary_files(signed_manifest, ibb, signed_ibb)
+
+    print("Assemble u-boot-verified.rom from:\n %s and %s"
+          % (u_boot_rom, signed_manifest))
+    assemble_secure_boot_image(u_boot_rom, signed_ibb)
-- 
2.7.4



More information about the U-Boot mailing list