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

Simon Glass sjg at chromium.org
Mon Nov 20 15:40:32 UTC 2017


Hi Anatolij,

On 16 November 2017 at 18:16, 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
>

This is a really nice use of binman, integrating various things to
make it work. It makes me wish we had this for FIT verified boot,
since at present you need manual steps.

To finish this, please add a test and info in the binman README about
the signing feature (x86-specific stuff can stay where it is).

> 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
> +               sign;
> +#ifdef CONFIG_SYS_SOC
> +               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"

Please use ' throughout if you can

> +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

space around +

> +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 Stage2 size is 0x1f400. For debug FSP it is 0x2f400,
> +# you must change it here wenn building with debug FSP image!
> +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
> +ROMFILE_SYS_TEXT_BASE = 0x00700000
> +
> +MANIFEST_IDENTIFIER = b'$VBM'
> +VERSION = 1
> +SECURE_VERSION_NUMBER = 2
> +OEM_DATA_PREAMBLE = '01000200'
> +
> +oem_data_hash_files = []

comment?

> +
> +
> +def append_binary_files(first_file, second_file, new_file):

function comment. Please fix globally. There is a standard format for
these, describing args and return value.

> +    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)

Should probably use

with open(OUTPUT_FILE_NAME, 'wb') as fd:
   fd.write(data)

so that the file gets closed here.

> +
> +
> +# 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

Instead of this, can you create a tmpdir and remove the whole directory?

> +    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):
> --
> 2.11.0
>

Regards,
Simon


More information about the U-Boot mailing list