[U-Boot] [PATCH v3 5/6] binman: add ROM image signing for Bay Trail SoC

Bin Meng bmeng.cn at gmail.com
Mon Dec 4 06:30:03 UTC 2017


Hi Anatolij,

On Fri, Nov 17, 2017 at 9:16 AM, Anatolij Gustschin <agust at denx.de> wrote:
> Generate u-boot-verified.rom image containing Secure Boot Manifest
> when secure boot option is enabled.
>
> Signed-off-by: Anatolij Gustschin <agust at denx.de>
> ---
> NOTE: This patch applies on top of binman changes in binman-working
> branch in git://git.denx.de/u-boot-dm.git
>
> Changes in v3:
>  - New patch. Moved signing script functionality (secure_boot_helper.py
>    in first series) to binman. The signing is enabled automatically
>    via u-boot.dtsi when secure boot option is enabled
>  - Clean up all temporary files generated by signing script
>
>  arch/x86/dts/u-boot.dtsi         |   7 +
>  tools/binman/signing/baytrail.py | 313 +++++++++++++++++++++++++++++++++++++++
>  tools/binman/signing/signer.py   |   3 +
>  3 files changed, 323 insertions(+)
>  create mode 100644 tools/binman/signing/baytrail.py
>
> diff --git a/arch/x86/dts/u-boot.dtsi b/arch/x86/dts/u-boot.dtsi
> index 7e37d4f394..98e2309108 100644
> --- a/arch/x86/dts/u-boot.dtsi
> +++ b/arch/x86/dts/u-boot.dtsi
> @@ -15,6 +15,13 @@
>                 sort-by-pos;
>                 pad-byte = <0xff>;
>                 size = <CONFIG_ROM_SIZE>;
> +#ifdef CONFIG_BAYTRAIL_SECURE_BOOT

This needs to be a generic macro like CONFIG_SECURE_BOOT as this
affects all x86 rom images.

> +               sign;
> +#ifdef CONFIG_SYS_SOC

I believe CONFIG_SYS_SOC is defined by every board, so no need to do
#ifdef here.

> +               socname = CONFIG_SYS_SOC;
> +#endif
> +#endif
> +
>  #ifdef CONFIG_HAVE_INTEL_ME
>                 intel-descriptor {
>                         filename = CONFIG_FLASH_DESCRIPTOR_FILE;
> diff --git a/tools/binman/signing/baytrail.py b/tools/binman/signing/baytrail.py
> new file mode 100644
> index 0000000000..3bfbbedb5d
> --- /dev/null
> +++ b/tools/binman/signing/baytrail.py
> @@ -0,0 +1,313 @@
> +# Copyright (c) 2017 DENX Software Engineering
> +# Written by Markus Valentin <mv at denx.de>
> +# Adapted for binman integration: Anatolij Gustschin <agust at denx.de>
> +#
> +# SPDX-License-Identifier:     GPL-2.0+
> +#
> +# Functions for signing the binman output image for Bay Trail SoC
> +#
> +
> +import binascii
> +import logging, sys
> +import os
> +
> +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'
> +OUTPUT_FILE_NAME = 'u-boot-verified.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'

This deserves a comment block on how there files are generated on the host.

> +
> +FIT_PUB_KEY_FILE_NAME = "dev.crt"
> +
> +# FSP Stage2 size is 0x1f400. For debug FSP it is 0x2f400,
> +# you must change it here wenn building with debug FSP image!

typo: wenn -> when

> +FSP_STAGE_2_SIZE = 0x1f400
> +FSP_UPD_SIZE = 0xc00
> +IBB_SIZE = 0x1fc00
> +MANIFEST_SIZE = 0x400
> +OEM_BLOCK_MAX_SIZE = 0x190
> +U_BOOT_ROM_SIZE = 0x800000

Can this file size be determined from the CONFIG_ROM_SIZE?

> +ROMFILE_SYS_TEXT_BASE = 0x00700000

and calculate this by ourselves?

> +
> +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 the OEM-Data block which must be inserted
> +# into the Bay Trail Secure Boot Manifest.
> +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 ROM image 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(OUTPUT_FILE_NAME, 'wb').write(data)
> +
> +
> +# Constructs a complete Secure Boot Manifest which is just missing
> +# the OEM publickey and the manifest signature
> +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 part of a binary from from_byte to 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 requested, change endianness
> +# (sometimes needed because the TXE engine wants another byteorder)
> +def sha256_to_file(binary_dir, file_to_hash, change_endianness=False):
> +    # We collect the hashes in a list (in the correct order) to be able
> +    # to put them later to the OEM data 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_endianness:
> +            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 hash filename 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 value must be divisible by 4
> +def pad_file_with_zeros(file_handle, byte_count):
> +    if byte_count % 4 != 0:
> +        raise ValueError("Given byte_count must be divisible by 4 ...")
> +    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 public key
> +# 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)
> +    fpf_config_file = open(fpf_config_path, 'r').readlines()
> +
> +    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:
> +        for line in fpf_config_file:
> +            if line.startswith('FUSE_FILE_OEM_KEY_HASH_1'):
> +                line = new_line_hash
> +            if line.startswith('FUSE_FILE_SECURE_BOOT_EN'):
> +                line = new_line_sb_enabled
> +            f.write(line)
> +
> +
> +# For the TXE engine one needs to change the endianness
> +def reverse_endianness(file_to_reverse):
> +    data = open(file_to_reverse, 'rb').read()
> +    open(file_to_reverse, 'wb').write(data[::-1])
> +
> +
> +# Sign the given file with the private_key using OpenSSL
> +# and write it to the signature_file
> +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)
> +
> +
> +# Perform IBB signing and generate Secure Boot Manifest for
> +# Bay Trail SoC, then create final ROM image containing the
> +# manifest and write it to u-boot-verified.rom file.
> +def baytrail_sign(u_boot_rom, keydir, indir, outdir):
> +    FORMAT = '  BINMAN  %(message)s'
> +    logging.basicConfig(stream=sys.stderr, level=logging.INFO, format=FORMAT)
> +    logging.info("Signing image %s" % u_boot_rom)
> +    for dir in indir:
> +        fsp = pjoin(dir, FSP_FILE_NAME)
> +        if not isfile(fsp):
> +            continue
> +
> +    if not isfile(fsp):
> +        raise ValueError("Can't find FSP file %s" % fsp)
> +
> +    # Assemble file paths
> +    fit_public_key = pjoin(keydir, FIT_PUB_KEY_FILE_NAME)
> +    fit_public_key_modulus = pjoin(keydir, FIT_PUB_KEY_FILE_NAME+".mod")
> +    fsp_stage2 = pjoin(outdir, FSP_STAGE2_FILE_NAME)
> +    u_boot_to_sign = pjoin(outdir, U_BOOT_TO_SIGN_FILE_NAME)
> +    ibb = pjoin(outdir, IBB_FILE_NAME)
> +    signed_ibb = pjoin(outdir, "signed_" + IBB_FILE_NAME)
> +
> +    signed_manifest = pjoin(outdir, SIGNED_MANIFEST_FILE_NAME)
> +    unsigned_manifest = pjoin(outdir, UNSIGNED_MANIFEST_FILE_NAME)
> +    manifest_signature = splitext(unsigned_manifest)[0] + ".signature"
> +
> +    oem_file = pjoin(outdir, OEM_FILE_NAME)
> +    oem_private_key = pjoin(keydir, OEM_PRIV_KEY_FILE_NAME)
> +    oem_public_key = pjoin(keydir, OEM_PUB_KEY_FILE_NAME)
> +    oem_pubkey_binary = pjoin(keydir, OEM_PUBKEY_BIN_FILE_NAME)
> +    oem_pubkey_and_sig = pjoin(keydir, OEM_PUBKEY_AND_SIG_FILE_NAME)
> +
> +    # Check for all needed files to be available
> +    for f in [fsp, u_boot_rom, fit_public_key, oem_private_key]:
> +        if not isfile(f):
> +            raise ValueError("File %s not found..." % f)
> +
> +    # Get everything from ROM file except IBB + Manfifest + UPD + FSP Stage2
> +    # (127KiB + 1KiB + 3KiB + 125Kib), then write it to a separated file and
> +    # calculate the hash. FPS Stage2 is verified in FSP, so skip it here
> +    extract_binary_part(u_boot_rom, u_boot_to_sign, ROMFILE_SYS_TEXT_BASE,
> +                        (U_BOOT_ROM_SIZE - (IBB_SIZE + MANIFEST_SIZE +
> +                                            FSP_UPD_SIZE + FSP_STAGE_2_SIZE)))
> +    sha256_to_file(outdir, u_boot_to_sign, True)
> +
> +    # Extract Stage2 of the FSP and calculate its hash
> +    extract_binary_part(fsp, fsp_stage2, 0, FSP_STAGE_2_SIZE)
> +    sha256_to_file(outdir, 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(outdir, fit_public_key_modulus, True)
> +
> +    # Assemble oemdata
> +    logging.debug("Assembling OEM data from %d hashes: %s" %
> +                  (oem_data_hash_files.__len__(), oem_data_hash_files))
> +    assemble_oem_data(oem_file)
> +
> +    logging.debug("Extracting last 127K 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)
> +
> +    logging.debug("Creating Secure Boot Manifest")
> +    create_unsigned_secure_boot_manifest(unsigned_manifest, oem_file, ibb)
> +
> +    logging.debug("Signing manifest with OpenSSL and private key %s" %
> +                  (oem_private_key))
> +    sign_file(unsigned_manifest, oem_private_key, manifest_signature)
> +
> +    logging.debug("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_endianness(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)
> +
> +    if isfile(FPF_CONFIG_FILE_NAME):
> +        hash_function = sha256()
> +        hash_function.update(bytearray(open(oem_pubkey_binary, 'rb').read()))
> +        replace_oem_pubkey_hash(hash_function.digest()[::-1],
> +                                FPF_CONFIG_FILE_NAME, False)
> +
> +    logging.debug("Append manifest with signature to IBB")
> +    append_binary_files(signed_manifest, ibb, signed_ibb)
> +
> +    logging.debug("Assemble %s from %s and %s" %
> +                  (OUTPUT_FILE_NAME, u_boot_rom, signed_manifest))
> +    assemble_secure_boot_image(u_boot_rom, signed_ibb)
> +
> +    # Cleanup temporary files
> +    os.remove(fsp_stage2)
> +    os.remove(ibb)
> +    os.remove(signed_ibb)
> +    os.remove(signed_manifest)
> +    os.remove(manifest_signature)
> +    os.remove(oem_file)
> +    os.remove(u_boot_to_sign)
> +    os.remove(unsigned_manifest)
> +    os.remove(FIT_PUB_KEY_FILE_NAME + ".sha256")
> +    os.remove(splitext(fsp_stage2)[0] + ".sha256")
> +    os.remove(splitext(u_boot_to_sign)[0] + ".sha256")
> diff --git a/tools/binman/signing/signer.py b/tools/binman/signing/signer.py
> index 4ec43d424f..e9ce97f559 100644
> --- a/tools/binman/signing/signer.py
> +++ b/tools/binman/signing/signer.py
> @@ -6,10 +6,13 @@
>  # Class for signing the output image of binman
>  #
>
> +from baytrail import baytrail_sign
> +
>  # Dictionary with SoC names and corresponding signing functions.
>  # Image signing support for not yet supported SoCs can be added
>  # here
>  soc_sign_dict = {
> +    'baytrail': baytrail_sign,
>  }
>
>  class ImageSigner(object):
> --

Regards,
Bin


More information about the U-Boot mailing list