mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 21:09:03 +02:00

Avoid using "cleansstate" in tests, as it can remove files from SSTATE_DIR and disrupt parallel builds on autobuilders. Use "bitbake kernel-signing-keys-native -c compile -f" to force key regeneration without affecting shared state. This issue was introduced in: oe-selftest: fitimage: cleanup FIT_GENERATE_KEYS OE-Core rev: 97e58d7c2bc1943f0696fc72984788f459f7f7c4 (From OE-Core rev: 917e2989f34fde12d3f039744fca1d5ab5b4a7a8) Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
1695 lines
75 KiB
Python
1695 lines
75 KiB
Python
#
|
|
# Copyright OpenEmbedded Contributors
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
|
|
import os
|
|
import re
|
|
import shlex
|
|
import logging
|
|
import pprint
|
|
import tempfile
|
|
|
|
import oe.fitimage
|
|
|
|
from oeqa.selftest.case import OESelftestTestCase
|
|
from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_bb_var
|
|
|
|
|
|
class BbVarsMockGenKeys:
|
|
def __init__(self, keydir, gen_keys="0", sign_enabled="0", keyname="", sign_ind="0", img_keyname=""):
|
|
self.bb_vars = {
|
|
'FIT_GENERATE_KEYS': gen_keys,
|
|
'FIT_KEY_GENRSA_ARGS': "-F4",
|
|
'FIT_KEY_REQ_ARGS': "-batch -new",
|
|
'FIT_KEY_SIGN_PKCS': "-x509",
|
|
'FIT_SIGN_INDIVIDUAL': sign_ind,
|
|
'FIT_SIGN_NUMBITS': "2048",
|
|
'UBOOT_SIGN_ENABLE': sign_enabled,
|
|
'UBOOT_SIGN_IMG_KEYNAME': img_keyname,
|
|
'UBOOT_SIGN_KEYDIR': keydir,
|
|
'UBOOT_SIGN_KEYNAME': keyname,
|
|
}
|
|
|
|
def getVar(self, var):
|
|
return self.bb_vars[var]
|
|
|
|
class FitImageTestCase(OESelftestTestCase):
|
|
"""Test functions usable for testing kernel-fitimage.bbclass and uboot-sign.bbclass
|
|
|
|
A brief summary showing the structure of a test case:
|
|
|
|
self._test_fitimage()
|
|
# Generate a local.conf file and bitbake the bootloader or the kernel
|
|
self._bitbake_fit_image()
|
|
|
|
# Check if the its file contains the expected paths and attributes.
|
|
# The _get_req_* functions are implemented by more specific chield classes.
|
|
self._check_its_file()
|
|
req_its_paths = self._get_req_its_paths()
|
|
req_sigvalues_config = self._get_req_sigvalues_config()
|
|
req_sigvalues_image = self._get_req_sigvalues_image()
|
|
# Compare the its file against req_its_paths, req_sigvalues_config, req_sigvalues_image
|
|
|
|
# Call the dumpimage utiliy and check that it prints all the expected paths and attributes
|
|
# The _get_req_* functions are implemented by more specific chield classes.
|
|
self._check_fitimage()
|
|
self._get_req_sections()
|
|
# Compare the output of the dumpimage utility against
|
|
"""
|
|
|
|
MKIMAGE_HASH_LENGTHS = { 'sha256': 64, 'sha384': 96, 'sha512': 128 }
|
|
MKIMAGE_SIGNATURE_LENGTHS = { 'rsa2048': 512 }
|
|
|
|
def _gen_signing_key(self, bb_vars):
|
|
"""Generate a key pair and a singing certificate
|
|
|
|
Generate a UBOOT_SIGN_KEYNAME in the UBOOT_SIGN_KEYDIR similar to what
|
|
the FIT_GENERATE_KEYS feature does. However, having a static key is
|
|
probably a more realistic use case than generating a random key with
|
|
each clean build. So this needs to be tested as well.
|
|
The FIT_GENERATE_KEYS generates 2 keys: The UBOOT_SIGN_KEYNAME and the
|
|
UBOOT_SIGN_IMG_KEYNAME. The UBOOT_SIGN_IMG_KEYNAME is used by the
|
|
FIT_SIGN_INDIVIDUAL feature only. Testing if everything is working if
|
|
there is only one key available is important as well. Therefore this
|
|
function generates only the keys which are really needed, not just two.
|
|
"""
|
|
|
|
# Define some variables which are usually defined by the kernel-fitimage.bbclass.
|
|
# But for testing purpose check if the uboot-sign.bbclass is independent from
|
|
# the kernel-fitimage.bbclass
|
|
fit_sign_numbits = bb_vars.get('FIT_SIGN_NUMBITS', "2048")
|
|
fit_key_genrsa_args = bb_vars.get('FIT_KEY_GENRSA_ARGS', "-F4")
|
|
fit_key_req_args = bb_vars.get('FIT_KEY_REQ_ARGS', "-batch -new")
|
|
fit_key_sign_pkcs = bb_vars.get('FIT_KEY_SIGN_PKCS', "-x509")
|
|
|
|
uboot_sign_keydir = bb_vars['UBOOT_SIGN_KEYDIR']
|
|
sign_keys = [bb_vars['UBOOT_SIGN_KEYNAME']]
|
|
if bb_vars['FIT_SIGN_INDIVIDUAL'] == "1":
|
|
sign_keys.append(bb_vars['UBOOT_SIGN_IMG_KEYNAME'])
|
|
for sign_key in sign_keys:
|
|
sing_key_path = os.path.join(uboot_sign_keydir, sign_key)
|
|
if not os.path.isdir(uboot_sign_keydir):
|
|
os.makedirs(uboot_sign_keydir)
|
|
openssl_bindir = FitImageTestCase._setup_native('openssl-native')
|
|
openssl_path = os.path.join(openssl_bindir, 'openssl')
|
|
runCmd("%s genrsa %s -out %s.key %s" % (
|
|
openssl_path,
|
|
fit_key_genrsa_args,
|
|
sing_key_path,
|
|
fit_sign_numbits
|
|
))
|
|
runCmd("%s req %s %s -key %s.key -out %s.crt" % (
|
|
openssl_path,
|
|
fit_key_req_args,
|
|
fit_key_sign_pkcs,
|
|
sing_key_path,
|
|
sing_key_path
|
|
))
|
|
|
|
@staticmethod
|
|
def _gen_random_file(file_path, num_bytes=65536):
|
|
with open(file_path, 'wb') as file_out:
|
|
file_out.write(os.urandom(num_bytes))
|
|
|
|
@staticmethod
|
|
def _setup_native(native_recipe):
|
|
"""Build a native recipe and return the path to its bindir in RECIPE_SYSROOT_NATIVE"""
|
|
bitbake(native_recipe + " -c addto_recipe_sysroot")
|
|
vars = get_bb_vars(['RECIPE_SYSROOT_NATIVE', 'bindir'], native_recipe)
|
|
return os.path.join(vars['RECIPE_SYSROOT_NATIVE'], vars['bindir'])
|
|
|
|
def _verify_fit_image_signature(self, uboot_tools_bindir, fitimage_path, dtb_path, conf_name=None):
|
|
"""Verify the signature of a fit configuration
|
|
|
|
The fit_check_sign utility from u-boot-tools-native is called.
|
|
uboot-fit_check_sign -f fitImage -k $dtb_path -c conf-$dtb_name
|
|
dtb_path refers to a binary device tree containing the public key.
|
|
"""
|
|
fit_check_sign_path = os.path.join(uboot_tools_bindir, 'uboot-fit_check_sign')
|
|
cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path)
|
|
if conf_name:
|
|
cmd += ' -c %s' % conf_name
|
|
result = runCmd(cmd)
|
|
self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
|
|
self.assertIn("Signature check OK", result.output)
|
|
|
|
def _verify_dtb_property(self, dtc_bindir, dtb_path, node_path, property_name, req_property, absent=False):
|
|
"""Verify device tree properties
|
|
|
|
The fdtget utility from dtc-native is called and the property is compared.
|
|
"""
|
|
fdtget_path = os.path.join(dtc_bindir, 'fdtget')
|
|
cmd = '%s %s %s %s' % (fdtget_path, dtb_path, node_path, property_name)
|
|
if absent:
|
|
result = runCmd(cmd, ignore_status=True)
|
|
self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
|
|
self.assertIn("FDT_ERR_NOTFOUND", result.output)
|
|
else:
|
|
result = runCmd(cmd)
|
|
self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
|
|
self.assertEqual(req_property, result.output.strip())
|
|
|
|
@staticmethod
|
|
def _find_string_in_bin_file(file_path, search_string):
|
|
"""find strings in a binary file
|
|
|
|
Shell equivalent: strings "$1" | grep "$2" | wc -l
|
|
return number of matches
|
|
"""
|
|
found_positions = 0
|
|
with open(file_path, 'rb') as file:
|
|
content = file.read().decode('ascii', errors='ignore')
|
|
found_positions = content.count(search_string)
|
|
return found_positions
|
|
|
|
@staticmethod
|
|
def _get_uboot_mkimage_sign_args(uboot_mkimage_sign_args):
|
|
"""Retrive the string passed via -c to the mkimage command
|
|
|
|
Example: If a build configutation defines
|
|
UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
|
|
this function returns "a smart comment"
|
|
"""
|
|
a_comment = None
|
|
if uboot_mkimage_sign_args:
|
|
mkimage_args = shlex.split(uboot_mkimage_sign_args)
|
|
try:
|
|
c_index = mkimage_args.index('-c')
|
|
a_comment = mkimage_args[c_index+1]
|
|
except ValueError:
|
|
pass
|
|
return a_comment
|
|
|
|
@staticmethod
|
|
def _get_dtb_files(bb_vars):
|
|
"""Return a list of devicetree names
|
|
|
|
The list should be used to check the dtb and conf nodes in the FIT image or its file.
|
|
In addition to the entries from KERNEL_DEVICETREE, the external devicetree and the
|
|
external devicetree overlay added by the test recipe bbb-dtbs-as-ext are handled as well.
|
|
"""
|
|
kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE')
|
|
all_dtbs = []
|
|
dtb_symlinks = []
|
|
if kernel_devicetree:
|
|
all_dtbs += [os.path.basename(dtb) for dtb in kernel_devicetree.split()]
|
|
# Support only the test recipe which provides 1 devicetree and 1 devicetree overlay
|
|
pref_prov_dtb = bb_vars.get('PREFERRED_PROVIDER_virtual/dtb')
|
|
if pref_prov_dtb == "bbb-dtbs-as-ext":
|
|
all_dtbs += ["am335x-bonegreen-ext.dtb", "BBORG_RELAY-00A2.dtbo"]
|
|
dtb_symlinks.append("am335x-bonegreen-ext-alias.dtb")
|
|
return (all_dtbs, dtb_symlinks)
|
|
|
|
def _is_req_dict_in_dict(self, found_dict, req_dict):
|
|
"""
|
|
Check if all key-value pairs in the required dictionary are present in the found dictionary.
|
|
|
|
This function recursively checks if the required dictionary (`req_dict`) is a subset of the found dictionary (`found_dict`).
|
|
It supports nested dictionaries, strings, lists, and sets as values.
|
|
|
|
Args:
|
|
found_dict (dict): The dictionary to search within.
|
|
req_dict (dict): The dictionary containing the required key-value pairs.
|
|
"""
|
|
for key, value in req_dict.items():
|
|
self.assertIn(key, found_dict)
|
|
if isinstance(value, dict):
|
|
self._is_req_dict_in_dict(found_dict[key], value)
|
|
elif isinstance(value, str):
|
|
self.assertIn(value, found_dict[key])
|
|
elif isinstance(value, list):
|
|
self.assertLessEqual(set(value), set(found_dict[key]))
|
|
elif isinstance(value, set):
|
|
self.assertLessEqual(value, found_dict[key])
|
|
else:
|
|
self.assertEqual(value, found_dict[key])
|
|
|
|
def _check_its_file(self, bb_vars, its_file_path):
|
|
"""Check if the its file contains the expected sections and fields"""
|
|
# print the its file for debugging
|
|
if logging.DEBUG >= self.logger.level:
|
|
with open(its_file_path) as its_file:
|
|
self.logger.debug("its file: %s" % its_file.read())
|
|
|
|
# Generate a list of expected paths in the its file
|
|
req_its_paths = self._get_req_its_paths(bb_vars)
|
|
self.logger.debug("req_its_paths:\n%s\n" % pprint.pformat(req_its_paths, indent=4))
|
|
|
|
# Generate a dict of expected configuration signature nodes
|
|
req_sigvalues_config = self._get_req_sigvalues_config(bb_vars)
|
|
self.logger.debug("req_sigvalues_config:\n%s\n" % pprint.pformat(req_sigvalues_config, indent=4))
|
|
|
|
# Generate a dict of expected image signature nodes
|
|
req_sigvalues_image = self._get_req_sigvalues_image(bb_vars)
|
|
self.logger.debug("req_sigvalues_image:\n%s\n" % pprint.pformat(req_sigvalues_image, indent=4))
|
|
|
|
# Parse the its file for paths and signatures
|
|
its_path = []
|
|
its_paths = []
|
|
linect = 0
|
|
sigs = {}
|
|
with open(its_file_path) as its_file:
|
|
for line in its_file:
|
|
linect += 1
|
|
line = line.strip()
|
|
if line.endswith('};'):
|
|
its_path.pop()
|
|
elif line.endswith('{'):
|
|
its_path.append(line[:-1].strip())
|
|
its_paths.append(its_path[:])
|
|
# kernel-fitimage uses signature-1, uboot-sign uses signature
|
|
elif its_path and (its_path[-1] == 'signature-1' or its_path[-1] == 'signature'):
|
|
itsdotpath = '.'.join(its_path)
|
|
if not itsdotpath in sigs:
|
|
sigs[itsdotpath] = {}
|
|
if not '=' in line or not line.endswith(';'):
|
|
self.fail('Unexpected formatting in %s sigs section line %d:%s' % (its_file_path, linect, line))
|
|
key, value = line.split('=', 1)
|
|
sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
|
|
|
|
# Check if all expected paths are found in the its file
|
|
self.logger.debug("itspaths:\n%s\n" % pprint.pformat(its_paths, indent=4))
|
|
for req_path in req_its_paths:
|
|
if not req_path in its_paths:
|
|
self.fail('Missing path in its file: %s (%s)' % (req_path, its_file_path))
|
|
|
|
# Check if all the expected singnature nodes (images and configurations) are found
|
|
self.logger.debug("sigs:\n%s\n" % pprint.pformat(sigs, indent=4))
|
|
if req_sigvalues_config or req_sigvalues_image:
|
|
for its_path, values in sigs.items():
|
|
if bb_vars.get('FIT_CONF_PREFIX', "conf-") in its_path:
|
|
reqsigvalues = req_sigvalues_config
|
|
else:
|
|
reqsigvalues = req_sigvalues_image
|
|
for reqkey, reqvalue in reqsigvalues.items():
|
|
value = values.get(reqkey, None)
|
|
if value is None:
|
|
self.fail('Missing key "%s" in its file signature section %s (%s)' % (reqkey, its_path, its_file_path))
|
|
self.assertEqual(value, reqvalue)
|
|
|
|
# Generate a list of expected fields in the its file
|
|
req_its_fields = self._get_req_its_fields(bb_vars)
|
|
self.logger.debug("req_its_fields:\n%s\n" % pprint.pformat(req_its_fields, indent=4))
|
|
|
|
# Check if all expected fields are in the its file
|
|
if req_its_fields:
|
|
field_index = 0
|
|
field_index_last = len(req_its_fields) - 1
|
|
with open(its_file_path) as its_file:
|
|
for line in its_file:
|
|
if req_its_fields[field_index] in line:
|
|
if field_index < field_index_last:
|
|
field_index +=1
|
|
else:
|
|
break
|
|
self.assertEqual(field_index, field_index_last,
|
|
"Fields in Image Tree Source File %s did not match, error in finding %s"
|
|
% (its_file_path, req_its_fields[field_index]))
|
|
|
|
def _check_fitimage(self, bb_vars, fitimage_path, uboot_tools_bindir):
|
|
"""Run dumpimage on the final FIT image and parse the output into a dict"""
|
|
dumpimage_path = os.path.join(uboot_tools_bindir, 'dumpimage')
|
|
cmd = '%s -l %s' % (dumpimage_path, fitimage_path)
|
|
self.logger.debug("Analyzing output from dumpimage: %s" % cmd)
|
|
dumpimage_result = runCmd(cmd)
|
|
in_section = None
|
|
sections = {}
|
|
self.logger.debug("dumpimage output: %s" % dumpimage_result.output)
|
|
for line in dumpimage_result.output.splitlines():
|
|
# Find potentially hashed and signed sections
|
|
if line.startswith((' Configuration', ' Image')):
|
|
in_section = re.search(r'\((.*)\)', line).groups()[0]
|
|
# Key value lines start with two spaces otherwise the section ended
|
|
elif not line.startswith(" "):
|
|
in_section = None
|
|
# Handle key value lines of this section
|
|
elif in_section:
|
|
if not in_section in sections:
|
|
sections[in_section] = {}
|
|
try:
|
|
key, value = line.split(':', 1)
|
|
key = key.strip()
|
|
value = value.strip()
|
|
except ValueError as val_err:
|
|
# Handle multiple entries as e.g. for Loadables as a list
|
|
if key and line.startswith(" "):
|
|
value = sections[in_section][key] + "," + line.strip()
|
|
else:
|
|
raise ValueError(f"Error processing line: '{line}'. Original error: {val_err}")
|
|
sections[in_section][key] = value
|
|
|
|
# Check if the requested dictionary is a subset of the parsed dictionary
|
|
req_sections, num_signatures = self._get_req_sections(bb_vars)
|
|
self.logger.debug("req_sections: \n%s\n" % pprint.pformat(req_sections, indent=4))
|
|
self.logger.debug("dumpimage sections: \n%s\n" % pprint.pformat(sections, indent=4))
|
|
self._is_req_dict_in_dict(sections, req_sections)
|
|
|
|
# Call the signing related checks if the function is provided by a inherited class
|
|
self._check_signing(bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path)
|
|
|
|
def _get_req_its_paths(self, bb_vars):
|
|
self.logger.error("This function needs to be implemented")
|
|
return []
|
|
|
|
def _get_req_its_fields(self, bb_vars):
|
|
self.logger.error("This function needs to be implemented")
|
|
return []
|
|
|
|
def _get_req_sigvalues_config(self, bb_vars):
|
|
self.logger.error("This function needs to be implemented")
|
|
return {}
|
|
|
|
def _get_req_sigvalues_image(self, bb_vars):
|
|
self.logger.error("This function needs to be implemented")
|
|
return {}
|
|
|
|
def _get_req_sections(self, bb_vars):
|
|
self.logger.error("This function needs to be implemented")
|
|
return ({}, 0)
|
|
|
|
def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
|
|
"""Verify the signatures in the FIT image."""
|
|
self.fail("Function needs to be implemented by inheriting classes")
|
|
|
|
def _bitbake_fit_image(self, bb_vars):
|
|
"""Bitbake the FIT image and return the paths to the its file and the FIT image"""
|
|
self.fail("Function needs to be implemented by inheriting classes")
|
|
|
|
def _test_fitimage(self, bb_vars):
|
|
"""Check if the its file and the FIT image are created and signed correctly"""
|
|
fitimage_its_path, fitimage_path = self._bitbake_fit_image(bb_vars)
|
|
self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
|
|
self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
|
|
|
|
self.logger.debug("Checking its: %s" % fitimage_its_path)
|
|
self._check_its_file(bb_vars, fitimage_its_path)
|
|
|
|
# Setup u-boot-tools-native
|
|
uboot_tools_bindir = FitImageTestCase._setup_native('u-boot-tools-native')
|
|
|
|
# Verify the FIT image
|
|
self._check_fitimage(bb_vars, fitimage_path, uboot_tools_bindir)
|
|
|
|
class KernelFitImageBase(FitImageTestCase):
|
|
"""Test cases for the linux-yocto-fitimage recipe"""
|
|
|
|
def _fit_get_bb_vars(self, additional_vars=[]):
|
|
"""Retrieve BitBake variables specific to the test case.
|
|
|
|
Call the get_bb_vars function once and get all variables needed by the test case.
|
|
"""
|
|
internal_used = {
|
|
'DEPLOY_DIR_IMAGE',
|
|
'FIT_CONF_DEFAULT_DTB',
|
|
'FIT_CONF_PREFIX',
|
|
'FIT_DESC',
|
|
'FIT_HASH_ALG',
|
|
'FIT_KERNEL_COMP_ALG',
|
|
'FIT_SIGN_ALG',
|
|
'FIT_SIGN_INDIVIDUAL',
|
|
'FIT_UBOOT_ENV',
|
|
'INITRAMFS_IMAGE_BUNDLE',
|
|
'INITRAMFS_IMAGE_NAME',
|
|
'INITRAMFS_IMAGE',
|
|
'KERNEL_DEPLOYSUBDIR',
|
|
'KERNEL_DEVICETREE',
|
|
'KERNEL_FIT_LINK_NAME',
|
|
'MACHINE',
|
|
'PREFERRED_PROVIDER_virtual/dtb',
|
|
'UBOOT_ARCH',
|
|
'UBOOT_ENTRYPOINT',
|
|
'UBOOT_LOADADDRESS',
|
|
'UBOOT_MKIMAGE_KERNEL_TYPE',
|
|
'UBOOT_MKIMAGE_SIGN_ARGS',
|
|
'UBOOT_RD_ENTRYPOINT',
|
|
'UBOOT_RD_LOADADDRESS',
|
|
'UBOOT_SIGN_ENABLE',
|
|
'UBOOT_SIGN_IMG_KEYNAME',
|
|
'UBOOT_SIGN_KEYDIR',
|
|
'UBOOT_SIGN_KEYNAME',
|
|
}
|
|
bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), self.kernel_recipe)
|
|
self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4))
|
|
return bb_vars
|
|
|
|
def _config_add_kernel_classes(self, config):
|
|
config += '# Use kernel-fit-extra-artifacts.bbclass for the creation of the vmlinux artifact' + os.linesep
|
|
config += 'KERNEL_CLASSES = "kernel-fit-extra-artifacts"' + os.linesep
|
|
return config
|
|
|
|
@property
|
|
def kernel_recipe(self):
|
|
return "linux-yocto-fitimage"
|
|
|
|
def _config_add_uboot_env(self, config):
|
|
"""Generate an u-boot environment
|
|
|
|
Create a boot.cmd file that is packed into the FIT image as a source-able text file.
|
|
Updates the configuration to include the boot.cmd file.
|
|
"""
|
|
fit_uenv_file = "boot.cmd"
|
|
test_files_dir = "test-files"
|
|
fit_uenv_path = os.path.join(self.builddir, test_files_dir, fit_uenv_file)
|
|
|
|
config += '# Add an u-boot script to the fitImage' + os.linesep
|
|
config += 'FIT_UBOOT_ENV = "%s"' % fit_uenv_file + os.linesep
|
|
config += 'FILESEXTRAPATHS:prepend := "${TOPDIR}/%s:"' % test_files_dir + os.linesep
|
|
config += 'SRC_URI:append:pn-%s = " file://${FIT_UBOOT_ENV}"' % self.kernel_recipe + os.linesep
|
|
|
|
if not os.path.isdir(test_files_dir):
|
|
os.makedirs(test_files_dir)
|
|
self.logger.debug("Writing to: %s" % fit_uenv_path)
|
|
with open(fit_uenv_path, "w") as f:
|
|
f.write('echo "hello world"')
|
|
|
|
return config
|
|
|
|
def _bitbake_fit_image(self, bb_vars):
|
|
"""Bitbake the kernel and return the paths to the its file and the FIT image"""
|
|
bitbake(self.kernel_recipe)
|
|
|
|
# Find the right its file and the final fitImage and check if both files are available
|
|
deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
|
|
initramfs_image = bb_vars['INITRAMFS_IMAGE']
|
|
initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
|
|
initramfs_image_name = bb_vars['INITRAMFS_IMAGE_NAME']
|
|
kernel_fit_link_name = bb_vars['KERNEL_FIT_LINK_NAME']
|
|
if not initramfs_image and initramfs_image_bundle != "1":
|
|
fitimage_its_name = "fitImage-its-%s" % kernel_fit_link_name
|
|
fitimage_name = "fitImage"
|
|
elif initramfs_image and initramfs_image_bundle != "1":
|
|
fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
|
|
fitimage_name = "fitImage-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
|
|
elif initramfs_image and initramfs_image_bundle == "1":
|
|
fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
|
|
fitimage_name = "fitImage" # or fitImage-${KERNEL_IMAGE_LINK_NAME}${KERNEL_IMAGE_BIN_EXT}
|
|
else:
|
|
self.fail('Invalid configuration: INITRAMFS_IMAGE_BUNDLE = "1" and not INITRAMFS_IMAGE')
|
|
kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR']
|
|
if kernel_deploysubdir:
|
|
fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_its_name))
|
|
fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_name))
|
|
else:
|
|
fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_its_name))
|
|
fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_name))
|
|
return (fitimage_its_path, fitimage_path)
|
|
|
|
def _get_req_its_paths(self, bb_vars):
|
|
"""Generate a list of expected paths in the its file
|
|
|
|
Example:
|
|
[
|
|
['/', 'images', 'kernel-1', 'hash-1'],
|
|
['/', 'images', 'kernel-1', 'signature-1'],
|
|
]
|
|
"""
|
|
dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars)
|
|
fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
|
|
fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
|
|
initramfs_image = bb_vars['INITRAMFS_IMAGE']
|
|
initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
|
|
uboot_sign_enable = bb_vars.get('UBOOT_SIGN_ENABLE')
|
|
|
|
# image nodes
|
|
images = [ 'kernel-1' ]
|
|
if dtb_files:
|
|
images += [ 'fdt-' + dtb for dtb in dtb_files ]
|
|
if fit_uboot_env:
|
|
images.append('bootscr-' + fit_uboot_env)
|
|
if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if
|
|
images.append('setup-1')
|
|
if initramfs_image and initramfs_image_bundle != "1":
|
|
images.append('ramdisk-1')
|
|
|
|
# configuration nodes (one per DTB and also one per symlink)
|
|
if dtb_files:
|
|
configurations = [bb_vars['FIT_CONF_PREFIX'] + conf for conf in dtb_files + dtb_symlinks]
|
|
else:
|
|
configurations = [bb_vars['FIT_CONF_PREFIX'] + '1']
|
|
|
|
# Create a list of paths for all image and configuration nodes
|
|
req_its_paths = []
|
|
for image in images:
|
|
req_its_paths.append(['/', 'images', image, 'hash-1'])
|
|
if uboot_sign_enable == "1" and fit_sign_individual == "1":
|
|
req_its_paths.append(['/', 'images', image, 'signature-1'])
|
|
for configuration in configurations:
|
|
req_its_paths.append(['/', 'configurations', configuration, 'hash-1'])
|
|
if uboot_sign_enable == "1":
|
|
req_its_paths.append(['/', 'configurations', configuration, 'signature-1'])
|
|
return req_its_paths
|
|
|
|
def _get_req_its_fields(self, bb_vars):
|
|
initramfs_image = bb_vars['INITRAMFS_IMAGE']
|
|
initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
|
|
uboot_rd_loadaddress = bb_vars.get('UBOOT_RD_LOADADDRESS')
|
|
uboot_rd_entrypoint = bb_vars.get('UBOOT_RD_ENTRYPOINT')
|
|
|
|
its_field_check = [
|
|
'description = "%s";' % bb_vars['FIT_DESC'],
|
|
'description = "Linux kernel";',
|
|
'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";',
|
|
# 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', defined based on files in TMPDIR, not ideal...
|
|
'data = /incbin/("linux.bin");',
|
|
'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";',
|
|
'os = "linux";',
|
|
'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;',
|
|
'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;',
|
|
]
|
|
if initramfs_image and initramfs_image_bundle != "1":
|
|
its_field_check.append('type = "ramdisk";')
|
|
if uboot_rd_loadaddress:
|
|
its_field_check.append("load = <%s>;" % uboot_rd_loadaddress)
|
|
if uboot_rd_entrypoint:
|
|
its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint)
|
|
|
|
fit_conf_default_dtb = bb_vars.get('FIT_CONF_DEFAULT_DTB')
|
|
if fit_conf_default_dtb:
|
|
fit_conf_prefix = bb_vars.get('FIT_CONF_PREFIX', "conf-")
|
|
its_field_check.append('default = "' + fit_conf_prefix + fit_conf_default_dtb + '";')
|
|
|
|
its_field_check.append('kernel = "kernel-1";')
|
|
|
|
if initramfs_image and initramfs_image_bundle != "1":
|
|
its_field_check.append('ramdisk = "ramdisk-1";')
|
|
|
|
return its_field_check
|
|
|
|
def _get_req_sigvalues_config(self, bb_vars):
|
|
"""Generate a dictionary of expected configuration signature nodes"""
|
|
if bb_vars.get('UBOOT_SIGN_ENABLE') != "1":
|
|
return {}
|
|
sign_images = '"kernel", "fdt"'
|
|
if bb_vars['INITRAMFS_IMAGE'] and bb_vars['INITRAMFS_IMAGE_BUNDLE'] != "1":
|
|
sign_images += ', "ramdisk"'
|
|
if bb_vars['FIT_UBOOT_ENV']:
|
|
sign_images += ', "bootscr"'
|
|
req_sigvalues_config = {
|
|
'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']),
|
|
'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_KEYNAME'],
|
|
'sign-images': sign_images,
|
|
}
|
|
return req_sigvalues_config
|
|
|
|
def _get_req_sigvalues_image(self, bb_vars):
|
|
"""Generate a dictionary of expected image signature nodes"""
|
|
if bb_vars['FIT_SIGN_INDIVIDUAL'] != "1":
|
|
return {}
|
|
req_sigvalues_image = {
|
|
'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']),
|
|
'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_IMG_KEYNAME'],
|
|
}
|
|
return req_sigvalues_image
|
|
|
|
def _get_req_sections(self, bb_vars):
|
|
"""Generate a dictionary of expected sections in the output of dumpimage"""
|
|
dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars)
|
|
fit_hash_alg = bb_vars['FIT_HASH_ALG']
|
|
fit_sign_alg = bb_vars['FIT_SIGN_ALG']
|
|
fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
|
|
fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
|
|
initramfs_image = bb_vars['INITRAMFS_IMAGE']
|
|
initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
|
|
uboot_sign_enable = bb_vars['UBOOT_SIGN_ENABLE']
|
|
uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
|
|
uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
|
|
num_signatures = 0
|
|
req_sections = {
|
|
"kernel-1": {
|
|
"Type": "Kernel Image",
|
|
"OS": "Linux",
|
|
"Load Address": bb_vars['UBOOT_LOADADDRESS'],
|
|
"Entry Point": bb_vars['UBOOT_ENTRYPOINT'],
|
|
}
|
|
}
|
|
# Create one section per DTB
|
|
for dtb in dtb_files:
|
|
req_sections['fdt-' + dtb] = {
|
|
"Type": "Flat Device Tree",
|
|
}
|
|
# Add a script section if there is a script
|
|
if fit_uboot_env:
|
|
req_sections['bootscr-' + fit_uboot_env] = { "Type": "Script" }
|
|
# Add the initramfs
|
|
if initramfs_image and initramfs_image_bundle != "1":
|
|
req_sections['ramdisk-1'] = {
|
|
"Type": "RAMDisk Image",
|
|
"Load Address": bb_vars['UBOOT_RD_LOADADDRESS'],
|
|
"Entry Point": bb_vars['UBOOT_RD_ENTRYPOINT']
|
|
}
|
|
# Create a configuration section for each DTB
|
|
if dtb_files:
|
|
for dtb in dtb_files + dtb_symlinks:
|
|
conf_name = bb_vars['FIT_CONF_PREFIX'] + dtb
|
|
# Assume that DTBs with an "-alias" in its name are symlink DTBs created e.g. by the
|
|
# bbb-dtbs-as-ext test recipe. Make the configuration node pointing to the real DTB.
|
|
real_dtb = dtb.replace("-alias", "")
|
|
# dtb overlays do not refer to a kernel (yet?)
|
|
if dtb.endswith('.dtbo'):
|
|
req_sections[conf_name] = {
|
|
"FDT": 'fdt-' + real_dtb,
|
|
}
|
|
else:
|
|
req_sections[conf_name] = {
|
|
"Kernel": "kernel-1",
|
|
"FDT": 'fdt-' + real_dtb,
|
|
}
|
|
if initramfs_image and initramfs_image_bundle != "1":
|
|
req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1"
|
|
else:
|
|
conf_name = bb_vars['FIT_CONF_PREFIX'] + '1'
|
|
req_sections[conf_name] = {
|
|
"Kernel": "kernel-1"
|
|
}
|
|
if initramfs_image and initramfs_image_bundle != "1":
|
|
req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1"
|
|
|
|
# Add signing related properties if needed
|
|
if uboot_sign_enable == "1":
|
|
for section in req_sections:
|
|
req_sections[section]['Hash algo'] = fit_hash_alg
|
|
if section.startswith(bb_vars['FIT_CONF_PREFIX']):
|
|
req_sections[section]['Hash value'] = "unavailable"
|
|
req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname)
|
|
num_signatures += 1
|
|
elif fit_sign_individual == "1":
|
|
req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname)
|
|
num_signatures += 1
|
|
return (req_sections, num_signatures)
|
|
|
|
def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
|
|
"""Verify the signature nodes in the FIT image"""
|
|
if bb_vars['UBOOT_SIGN_ENABLE'] == "1":
|
|
self.logger.debug("Verifying signatures in the FIT image")
|
|
else:
|
|
self.logger.debug("FIT image is not signed. Signature verification is not needed.")
|
|
return
|
|
|
|
fit_hash_alg = bb_vars['FIT_HASH_ALG']
|
|
fit_sign_alg = bb_vars['FIT_SIGN_ALG']
|
|
uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
|
|
uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
|
|
deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
|
|
kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR']
|
|
fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
|
|
fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg]
|
|
fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg]
|
|
for section, values in sections.items():
|
|
# Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1")
|
|
if section.startswith(bb_vars['FIT_CONF_PREFIX']):
|
|
sign_algo = values.get('Sign algo', None)
|
|
req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname)
|
|
self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
|
|
sign_value = values.get('Sign value', None)
|
|
self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
|
|
dtb_file_name = section.replace(bb_vars['FIT_CONF_PREFIX'], '')
|
|
dtb_path = os.path.join(deploy_dir_image, dtb_file_name)
|
|
if kernel_deploysubdir:
|
|
dtb_path = os.path.join(deploy_dir_image, kernel_deploysubdir, dtb_file_name)
|
|
# External devicetrees created by devicetree.bbclass are in a subfolder and have priority
|
|
dtb_path_ext = os.path.join(deploy_dir_image, "devicetree", dtb_file_name)
|
|
if os.path.exists(dtb_path_ext):
|
|
dtb_path = dtb_path_ext
|
|
self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, dtb_path, section)
|
|
else:
|
|
# Image nodes always need a hash which gets indirectly signed by the config signature
|
|
hash_algo = values.get('Hash algo', None)
|
|
self.assertEqual(hash_algo, fit_hash_alg)
|
|
hash_value = values.get('Hash value', None)
|
|
self.assertEqual(len(hash_value), fit_hash_alg_len, 'Hash value for section %s not expected length' % section)
|
|
# Optionally, if FIT_SIGN_INDIVIDUAL = 1 also the image nodes have a signature (which is redundant but possible)
|
|
if fit_sign_individual == "1":
|
|
sign_algo = values.get('Sign algo', None)
|
|
req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname)
|
|
self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
|
|
sign_value = values.get('Sign value', None)
|
|
self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
|
|
|
|
# Search for the string passed to mkimage in each signed section of the FIT image.
|
|
# Looks like mkimage supports to add a comment but does not support to read it back.
|
|
a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['UBOOT_MKIMAGE_SIGN_ARGS'])
|
|
self.logger.debug("a_comment: %s" % a_comment)
|
|
if a_comment:
|
|
found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment)
|
|
self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." %
|
|
(num_signatures, a_comment))
|
|
|
|
class KernelFitImageRecipeTests(KernelFitImageBase):
|
|
"""Test cases for the kernel-fitimage bbclass"""
|
|
|
|
def test_fit_image(self):
|
|
"""
|
|
Summary: Check if FIT image and Image Tree Source (its) are built
|
|
and the Image Tree Source has the correct fields.
|
|
Expected: 1. fitImage and fitImage-its can be built
|
|
2. The type, load address, entrypoint address and
|
|
default values of kernel and ramdisk are as expected
|
|
in the Image Tree Source. Not all the fields are tested,
|
|
only the key fields that wont vary between different
|
|
architectures.
|
|
Product: oe-core
|
|
Author: Usama Arif <usama.arif@arm.com>
|
|
"""
|
|
config = """
|
|
KERNEL_IMAGETYPE = "Image"
|
|
|
|
# RAM disk variables including load address and entrypoint for kernel and RAM disk
|
|
IMAGE_FSTYPES += "cpio.gz"
|
|
INITRAMFS_IMAGE = "core-image-minimal"
|
|
# core-image-minimal is used as initramfs here, drop the rootfs suffix
|
|
IMAGE_NAME_SUFFIX:pn-core-image-minimal = ""
|
|
UBOOT_RD_LOADADDRESS = "0x88000000"
|
|
UBOOT_RD_ENTRYPOINT = "0x88000000"
|
|
UBOOT_LOADADDRESS = "0x80080000"
|
|
UBOOT_ENTRYPOINT = "0x80080000"
|
|
FIT_DESC = "A model description"
|
|
FIT_CONF_PREFIX = "foo-"
|
|
"""
|
|
config = self._config_add_kernel_classes(config)
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
self._test_fitimage(bb_vars)
|
|
|
|
def test_get_compatible_from_dtb(self):
|
|
"""Test the oe.fitimage.get_compatible_from_dtb function
|
|
|
|
1. bitbake bbb-dtbs-as-ext
|
|
2. Check if symlink_points_below returns the path to the DTB
|
|
3. Check if the expected compatible string is found by get_compatible_from_dtb()
|
|
"""
|
|
DTB_RECIPE = "bbb-dtbs-as-ext"
|
|
DTB_FILE = "am335x-bonegreen-ext.dtb"
|
|
DTB_SYMLINK = "am335x-bonegreen-ext-alias.dtb"
|
|
DTBO_FILE = "BBORG_RELAY-00A2.dtbo"
|
|
EXPECTED_COMP = ["ti,am335x-bone-green", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"]
|
|
|
|
config = """
|
|
DISTRO = "poky"
|
|
MACHINE = "beaglebone-yocto"
|
|
"""
|
|
self.write_config(config)
|
|
|
|
# Provide the fdtget command called by get_compatible_from_dtb
|
|
dtc_bindir = FitImageTestCase._setup_native('dtc-native')
|
|
fdtget_path = os.path.join(dtc_bindir, "fdtget")
|
|
self.assertExists(fdtget_path)
|
|
|
|
# bitbake an external DTB with a symlink to it and a DTB overlay
|
|
bitbake(DTB_RECIPE)
|
|
deploy_dir_image = get_bb_var("DEPLOY_DIR_IMAGE", DTB_RECIPE)
|
|
devicetree_dir = os.path.join(deploy_dir_image, "devicetree")
|
|
dtb_path = os.path.join(devicetree_dir, DTB_FILE)
|
|
dtb_alias_path = os.path.join(devicetree_dir, DTB_SYMLINK)
|
|
dtbo_file = os.path.join(devicetree_dir, DTBO_FILE)
|
|
self.assertExists(dtb_path)
|
|
self.assertExists(dtb_alias_path)
|
|
self.assertExists(dtbo_file)
|
|
|
|
# Test symlink_points_below
|
|
linked_dtb = oe.fitimage.symlink_points_below(dtb_alias_path, devicetree_dir)
|
|
self.assertEqual(linked_dtb, DTB_FILE)
|
|
|
|
# Check if get_compatible_from_dtb finds the expected compatible string in the DTBs
|
|
comp = oe.fitimage.get_compatible_from_dtb(dtb_path, fdtget_path)
|
|
self.assertEqual(comp, EXPECTED_COMP)
|
|
comp_alias = oe.fitimage.get_compatible_from_dtb(dtb_alias_path, fdtget_path)
|
|
self.assertEqual(comp_alias, EXPECTED_COMP)
|
|
# The alias is a symlink, therefore the compatible string is equal
|
|
self.assertEqual(comp_alias, comp)
|
|
|
|
def test_fit_image_ext_dtb_dtbo(self):
|
|
"""
|
|
Summary: Check if FIT image and Image Tree Source (its) are created correctly.
|
|
Expected: 1) its and FIT image are built successfully
|
|
2) The its file contains also the external devicetree overlay
|
|
3) Dumping the FIT image indicates the devicetree overlay
|
|
"""
|
|
config = """
|
|
# Enable creation of fitImage
|
|
MACHINE = "beaglebone-yocto"
|
|
# Add a devicetree overlay which does not need kernel sources
|
|
PREFERRED_PROVIDER_virtual/dtb = "bbb-dtbs-as-ext"
|
|
"""
|
|
config = self._config_add_kernel_classes(config)
|
|
config = self._config_add_uboot_env(config)
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
self._test_fitimage(bb_vars)
|
|
|
|
|
|
def test_sign_fit_image_configurations(self):
|
|
"""
|
|
Summary: Check if FIT image and Image Tree Source (its) are created
|
|
and the configuration nodes are signed correctly.
|
|
Expected: 1) its and FIT image are built successfully
|
|
2) Scanning the its file indicates signing is enabled
|
|
as requested by UBOOT_SIGN_ENABLE
|
|
3) Dumping the FIT image indicates signature values
|
|
are present (only for the configuration nodes as
|
|
FIT_SIGN_INDIVIDUAL is disabled)
|
|
4) Verify the FIT image contains the comments passed via
|
|
UBOOT_MKIMAGE_SIGN_ARGS once per configuration node.
|
|
"""
|
|
# Generate a configuration section which gets included into the local.conf file
|
|
config = """
|
|
# Enable creation of fitImage
|
|
MACHINE = "beaglebone-yocto"
|
|
UBOOT_SIGN_ENABLE = "1"
|
|
UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_SIGN_KEYNAME = "dev"
|
|
UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
|
|
FIT_CONF_DEFAULT_DTB = "am335x-bonegreen.dtb"
|
|
"""
|
|
config = self._config_add_kernel_classes(config)
|
|
config = self._config_add_uboot_env(config)
|
|
self.write_config(config)
|
|
|
|
# Retrieve some variables from bitbake
|
|
bb_vars = self._fit_get_bb_vars([
|
|
'FIT_KEY_GENRSA_ARGS',
|
|
'FIT_KEY_REQ_ARGS',
|
|
'FIT_KEY_SIGN_PKCS',
|
|
'FIT_SIGN_NUMBITS',
|
|
'UBOOT_SIGN_KEYDIR',
|
|
])
|
|
|
|
self._gen_signing_key(bb_vars)
|
|
self._test_fitimage(bb_vars)
|
|
|
|
def test_sign_fit_image_individual(self):
|
|
"""
|
|
Summary: Check if FIT image and Image Tree Source (its) are created
|
|
and all nodes are signed correctly.
|
|
Expected: 1) its and FIT image are built successfully
|
|
2) Scanning the its file indicates signing is enabled
|
|
as requested by UBOOT_SIGN_ENABLE
|
|
3) Dumping the FIT image indicates signature values
|
|
are present (including for images as enabled via
|
|
FIT_SIGN_INDIVIDUAL)
|
|
This also implies that FIT_GENERATE_KEYS = "1" works.
|
|
4) Verify the FIT image contains the comments passed via
|
|
UBOOT_MKIMAGE_SIGN_ARGS once per image and per
|
|
configuration node.
|
|
Note: This test is mostly for backward compatibility.
|
|
The recommended approach is to sign the configuration nodes
|
|
which include also the hashes of all the images. Signing
|
|
all the images individually is therefore redundant.
|
|
Product: oe-core
|
|
Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon
|
|
work by Usama Arif <usama.arif@arm.com>
|
|
"""
|
|
# Generate a configuration section which gets included into the local.conf file
|
|
config = """
|
|
# Enable creation of fitImage
|
|
MACHINE = "beaglebone-yocto"
|
|
UBOOT_SIGN_ENABLE = "1"
|
|
FIT_GENERATE_KEYS = "1"
|
|
UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
|
|
UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
|
|
FIT_SIGN_INDIVIDUAL = "1"
|
|
UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
|
|
"""
|
|
config = self._config_add_kernel_classes(config)
|
|
config = self._config_add_uboot_env(config)
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
|
|
# Ensure new keys are generated and FIT_GENERATE_KEYS = "1" is tested
|
|
bitbake("kernel-signing-keys-native -c compile -f")
|
|
|
|
self._test_fitimage(bb_vars)
|
|
|
|
def test_fit_image_sign_initramfs(self):
|
|
"""
|
|
Summary: Verifies the content of the initramfs node in the FIT Image Tree Source (its)
|
|
The FIT settings are set by the test case.
|
|
The machine used is beaglebone-yocto.
|
|
Expected: 1. The ITS is generated with initramfs support
|
|
2. All the fields in the kernel node are as expected (matching the
|
|
conf settings)
|
|
3. The kernel is included in all the available configurations and
|
|
its hash is included in the configuration signature
|
|
|
|
Product: oe-core
|
|
Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
|
|
"""
|
|
|
|
config = """
|
|
DISTRO = "poky"
|
|
MACHINE = "beaglebone-yocto"
|
|
INITRAMFS_IMAGE = "core-image-minimal-initramfs"
|
|
INITRAMFS_SCRIPTS = ""
|
|
UBOOT_MACHINE = "am335x_evm_defconfig"
|
|
UBOOT_SIGN_ENABLE = "1"
|
|
UBOOT_SIGN_KEYNAME = "beaglebonekey"
|
|
UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
|
|
UBOOT_DTB_BINARY = "u-boot.dtb"
|
|
UBOOT_ENTRYPOINT = "0x80000000"
|
|
UBOOT_LOADADDRESS = "0x80000000"
|
|
UBOOT_RD_LOADADDRESS = "0x88000000"
|
|
UBOOT_RD_ENTRYPOINT = "0x88000000"
|
|
UBOOT_DTB_LOADADDRESS = "0x82000000"
|
|
UBOOT_ARCH = "arm"
|
|
UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
|
|
UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
|
|
UBOOT_EXTLINUX = "0"
|
|
KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
|
|
FIT_KERNEL_COMP_ALG = "none"
|
|
FIT_HASH_ALG = "sha256"
|
|
"""
|
|
config = self._config_add_kernel_classes(config)
|
|
config = self._config_add_uboot_env(config)
|
|
self.write_config(config)
|
|
|
|
# Retrieve some variables from bitbake
|
|
bb_vars = self._fit_get_bb_vars([
|
|
'FIT_KEY_GENRSA_ARGS',
|
|
'FIT_KEY_REQ_ARGS',
|
|
'FIT_KEY_SIGN_PKCS',
|
|
'FIT_SIGN_NUMBITS',
|
|
'UBOOT_SIGN_KEYDIR',
|
|
])
|
|
|
|
self._gen_signing_key(bb_vars)
|
|
self._test_fitimage(bb_vars)
|
|
|
|
def test_fit_image_sign_initramfs_bundle(self):
|
|
"""
|
|
Summary: Verifies the content of the initramfs bundle node in the FIT Image Tree Source (its)
|
|
The FIT settings are set by the test case.
|
|
The machine used is beaglebone-yocto.
|
|
Expected: 1. The ITS is generated with initramfs bundle support
|
|
2. All the fields in the kernel node are as expected (matching the
|
|
conf settings)
|
|
3. The kernel is included in all the available configurations and
|
|
its hash is included in the configuration signature
|
|
|
|
Product: oe-core
|
|
Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
|
|
"""
|
|
|
|
config = """
|
|
DISTRO = "poky"
|
|
MACHINE = "beaglebone-yocto"
|
|
INITRAMFS_IMAGE_BUNDLE = "1"
|
|
INITRAMFS_IMAGE = "core-image-minimal-initramfs"
|
|
INITRAMFS_SCRIPTS = ""
|
|
UBOOT_MACHINE = "am335x_evm_defconfig"
|
|
UBOOT_SIGN_ENABLE = "1"
|
|
UBOOT_SIGN_KEYNAME = "beaglebonekey"
|
|
UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
|
|
UBOOT_DTB_BINARY = "u-boot.dtb"
|
|
UBOOT_ENTRYPOINT = "0x80000000"
|
|
UBOOT_LOADADDRESS = "0x80000000"
|
|
UBOOT_DTB_LOADADDRESS = "0x82000000"
|
|
UBOOT_ARCH = "arm"
|
|
UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
|
|
UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
|
|
UBOOT_EXTLINUX = "0"
|
|
KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
|
|
FIT_KERNEL_COMP_ALG = "none"
|
|
FIT_HASH_ALG = "sha256"
|
|
"""
|
|
config = self._config_add_kernel_classes(config)
|
|
config = self._config_add_uboot_env(config)
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
self._gen_signing_key(bb_vars)
|
|
self._test_fitimage(bb_vars)
|
|
|
|
class FitImagePyTests(KernelFitImageBase):
|
|
"""Test cases for the fitimage.py module without calling bitbake"""
|
|
|
|
def _test_fitimage_py(self, bb_vars_overrides=None):
|
|
topdir = os.path.join(os.environ['BUILDDIR'])
|
|
fitimage_its_path = os.path.join(topdir, self._testMethodName + '.its')
|
|
|
|
# Provide variables without calling bitbake
|
|
bb_vars = {
|
|
# image-fitimage.conf
|
|
'FIT_DESC': "Kernel fitImage for a dummy distro",
|
|
'FIT_HASH_ALG': "sha256",
|
|
'FIT_SIGN_ALG': "rsa2048",
|
|
'FIT_PAD_ALG': "pkcs-1.5",
|
|
'FIT_GENERATE_KEYS': "0",
|
|
'FIT_SIGN_NUMBITS': "2048",
|
|
'FIT_KEY_GENRSA_ARGS': "-F4",
|
|
'FIT_KEY_REQ_ARGS': "-batch -new",
|
|
'FIT_KEY_SIGN_PKCS': "-x509",
|
|
'FIT_SIGN_INDIVIDUAL': "0",
|
|
'FIT_CONF_PREFIX': "conf-",
|
|
'FIT_SUPPORTED_INITRAMFS_FSTYPES': "cpio.lz4 cpio.lzo cpio.lzma cpio.xz cpio.zst cpio.gz ext2.gz cpio",
|
|
'FIT_CONF_DEFAULT_DTB': "",
|
|
'FIT_ADDRESS_CELLS': "1",
|
|
'FIT_UBOOT_ENV': "",
|
|
# kernel.bbclass
|
|
'UBOOT_ENTRYPOINT': "0x20008000",
|
|
'UBOOT_LOADADDRESS': "0x20008000",
|
|
'INITRAMFS_IMAGE': "",
|
|
'INITRAMFS_IMAGE_BUNDLE': "",
|
|
# kernel-uboot.bbclass
|
|
'FIT_KERNEL_COMP_ALG': "gzip",
|
|
'FIT_KERNEL_COMP_ALG_EXTENSION': ".gz",
|
|
'UBOOT_MKIMAGE_KERNEL_TYPE': "kernel",
|
|
# uboot-config.bbclass
|
|
'UBOOT_MKIMAGE_DTCOPTS': "",
|
|
'UBOOT_MKIMAGE': "uboot-mkimage",
|
|
'UBOOT_MKIMAGE_SIGN': "uboot-mkimage",
|
|
'UBOOT_MKIMAGE_SIGN_ARGS': "",
|
|
'UBOOT_SIGN_ENABLE': "0",
|
|
'UBOOT_SIGN_KEYDIR': None,
|
|
'UBOOT_SIGN_KEYNAME': None,
|
|
'UBOOT_SIGN_IMG_KEYNAME': None,
|
|
# others
|
|
'MACHINE': "qemux86-64",
|
|
'UBOOT_ARCH': "x86",
|
|
'HOST_PREFIX': "x86_64-poky-linux-"
|
|
}
|
|
if bb_vars_overrides:
|
|
bb_vars.update(bb_vars_overrides)
|
|
|
|
root_node = oe.fitimage.ItsNodeRootKernel(
|
|
bb_vars["FIT_DESC"], bb_vars["FIT_ADDRESS_CELLS"],
|
|
bb_vars['HOST_PREFIX'], bb_vars['UBOOT_ARCH'], bb_vars["FIT_CONF_PREFIX"],
|
|
oe.types.boolean(bb_vars['UBOOT_SIGN_ENABLE']), bb_vars["UBOOT_SIGN_KEYDIR"],
|
|
bb_vars["UBOOT_MKIMAGE"], bb_vars["UBOOT_MKIMAGE_DTCOPTS"],
|
|
bb_vars["UBOOT_MKIMAGE_SIGN"], bb_vars["UBOOT_MKIMAGE_SIGN_ARGS"],
|
|
bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG'], bb_vars['FIT_PAD_ALG'],
|
|
bb_vars['UBOOT_SIGN_KEYNAME'],
|
|
oe.types.boolean(bb_vars['FIT_SIGN_INDIVIDUAL']), bb_vars['UBOOT_SIGN_IMG_KEYNAME']
|
|
)
|
|
|
|
root_node.fitimage_emit_section_kernel("kernel-1", "linux.bin", "none",
|
|
bb_vars.get('UBOOT_LOADADDRESS'), bb_vars.get('UBOOT_ENTRYPOINT'),
|
|
bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE'), bb_vars.get("UBOOT_ENTRYSYMBOL")
|
|
)
|
|
|
|
dtb_files, _ = FitImageTestCase._get_dtb_files(bb_vars)
|
|
for dtb in dtb_files:
|
|
root_node.fitimage_emit_section_dtb(dtb, os.path.join("a-dir", dtb),
|
|
bb_vars.get("UBOOT_DTB_LOADADDRESS"), bb_vars.get("UBOOT_DTBO_LOADADDRESS"))
|
|
|
|
if bb_vars.get('FIT_UBOOT_ENV'):
|
|
root_node.fitimage_emit_section_boot_script(
|
|
"bootscr-" + bb_vars['FIT_UBOOT_ENV'], bb_vars['FIT_UBOOT_ENV'])
|
|
|
|
if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if
|
|
root_node.fitimage_emit_section_setup("setup-1", "setup1.bin")
|
|
|
|
if bb_vars.get('INITRAMFS_IMAGE') and bb_vars.get("INITRAMFS_IMAGE_BUNDLE") != "1":
|
|
root_node.fitimage_emit_section_ramdisk("ramdisk-1", "a-dir/a-initramfs-1",
|
|
"core-image-minimal-initramfs",
|
|
bb_vars.get("UBOOT_RD_LOADADDRESS"), bb_vars.get("UBOOT_RD_ENTRYPOINT"))
|
|
|
|
root_node.fitimage_emit_section_config(bb_vars['FIT_CONF_DEFAULT_DTB'])
|
|
root_node.write_its_file(fitimage_its_path)
|
|
|
|
self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
|
|
self.logger.debug("Checking its: %s" % fitimage_its_path)
|
|
self._check_its_file(bb_vars, fitimage_its_path)
|
|
|
|
def test_fitimage_py_default(self):
|
|
self._test_fitimage_py()
|
|
|
|
def test_fitimage_py_default_dtb(self):
|
|
bb_vars_overrides = {
|
|
'KERNEL_DEVICETREE': "one.dtb two.dtb three.dtb",
|
|
'FIT_CONF_DEFAULT_DTB': "two.dtb"
|
|
}
|
|
self._test_fitimage_py(bb_vars_overrides)
|
|
|
|
|
|
class UBootFitImageTests(FitImageTestCase):
|
|
"""Test cases for the uboot-sign bbclass"""
|
|
|
|
BOOTLOADER_RECIPE = "virtual/bootloader"
|
|
|
|
def _fit_get_bb_vars(self, additional_vars=[]):
|
|
"""Get bb_vars as needed by _test_sign_fit_image
|
|
|
|
Call the get_bb_vars function once and get all variables needed by the test case.
|
|
"""
|
|
internal_used = {
|
|
'DEPLOY_DIR_IMAGE',
|
|
'FIT_HASH_ALG',
|
|
'FIT_KEY_GENRSA_ARGS',
|
|
'FIT_KEY_REQ_ARGS',
|
|
'FIT_KEY_SIGN_PKCS',
|
|
'FIT_SIGN_ALG',
|
|
'FIT_SIGN_INDIVIDUAL',
|
|
'FIT_SIGN_NUMBITS',
|
|
'MACHINE',
|
|
'SPL_MKIMAGE_SIGN_ARGS',
|
|
'SPL_SIGN_ENABLE',
|
|
'SPL_SIGN_KEYNAME',
|
|
'UBOOT_ARCH',
|
|
'UBOOT_DTB_BINARY',
|
|
'UBOOT_DTB_IMAGE',
|
|
'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT',
|
|
'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS',
|
|
'UBOOT_FIT_ARM_TRUSTED_FIRMWARE',
|
|
'UBOOT_FIT_CONF_USER_LOADABLES',
|
|
'UBOOT_FIT_DESC',
|
|
'UBOOT_FIT_HASH_ALG',
|
|
'UBOOT_FIT_SIGN_ALG',
|
|
'UBOOT_FIT_TEE_ENTRYPOINT',
|
|
'UBOOT_FIT_TEE_LOADADDRESS',
|
|
'UBOOT_FIT_TEE',
|
|
'UBOOT_FIT_UBOOT_ENTRYPOINT',
|
|
'UBOOT_FIT_UBOOT_LOADADDRESS',
|
|
'UBOOT_FIT_USER_SETTINGS',
|
|
'UBOOT_FITIMAGE_ENABLE',
|
|
'UBOOT_NODTB_BINARY',
|
|
'UBOOT_SIGN_ENABLE',
|
|
'UBOOT_SIGN_IMG_KEYNAME',
|
|
'UBOOT_SIGN_KEYDIR',
|
|
'UBOOT_SIGN_KEYNAME',
|
|
}
|
|
bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), UBootFitImageTests.BOOTLOADER_RECIPE)
|
|
self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4))
|
|
return bb_vars
|
|
|
|
def _bitbake_fit_image(self, bb_vars):
|
|
"""Bitbake the bootloader and return the paths to the its file and the FIT image"""
|
|
bitbake(UBootFitImageTests.BOOTLOADER_RECIPE)
|
|
|
|
deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
|
|
machine = bb_vars['MACHINE']
|
|
fitimage_its_path = os.path.join(deploy_dir_image, "u-boot-its-%s" % machine)
|
|
fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % machine)
|
|
return (fitimage_its_path, fitimage_path)
|
|
|
|
def _get_req_its_paths(self, bb_vars):
|
|
# image nodes
|
|
images = [ 'uboot', 'fdt', ]
|
|
if bb_vars['UBOOT_FIT_TEE'] == "1":
|
|
images.append('tee')
|
|
if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
|
|
images.append('atf')
|
|
# if bb_vars['UBOOT_FIT_USER_SETTINGS']:
|
|
|
|
# configuration nodes
|
|
configurations = [ 'conf']
|
|
|
|
# Create a list of paths for all image and configuration nodes
|
|
req_its_paths = []
|
|
for image in images:
|
|
req_its_paths.append(['/', 'images', image])
|
|
if bb_vars['SPL_SIGN_ENABLE'] == "1":
|
|
req_its_paths.append(['/', 'images', image, 'signature'])
|
|
for configuration in configurations:
|
|
req_its_paths.append(['/', 'configurations', configuration])
|
|
return req_its_paths
|
|
|
|
def _get_req_its_fields(self, bb_vars):
|
|
loadables = ["uboot"]
|
|
its_field_check = [
|
|
'description = "%s";' % bb_vars['UBOOT_FIT_DESC'],
|
|
'description = "U-Boot image";',
|
|
'data = /incbin/("%s");' % bb_vars['UBOOT_NODTB_BINARY'],
|
|
'type = "standalone";',
|
|
'os = "u-boot";',
|
|
'arch = "%s";' % bb_vars['UBOOT_ARCH'],
|
|
'compression = "none";',
|
|
'load = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'],
|
|
'entry = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'],
|
|
'description = "U-Boot FDT";',
|
|
'data = /incbin/("%s");' % bb_vars['UBOOT_DTB_BINARY'],
|
|
'type = "flat_dt";',
|
|
'arch = "%s";' % bb_vars['UBOOT_ARCH'],
|
|
'compression = "none";',
|
|
]
|
|
if bb_vars['UBOOT_FIT_TEE'] == "1":
|
|
its_field_check += [
|
|
'description = "Trusted Execution Environment";',
|
|
'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_TEE_IMAGE'],
|
|
'type = "tee";',
|
|
'arch = "%s";' % bb_vars['UBOOT_ARCH'],
|
|
'os = "tee";',
|
|
'load = <%s>;' % bb_vars['UBOOT_FIT_TEE_LOADADDRESS'],
|
|
'entry = <%s>;' % bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'],
|
|
'compression = "none";',
|
|
]
|
|
loadables.insert(0, "tee")
|
|
if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
|
|
its_field_check += [
|
|
'description = "ARM Trusted Firmware";',
|
|
'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'],
|
|
'type = "firmware";',
|
|
'arch = "%s";' % bb_vars['UBOOT_ARCH'],
|
|
'os = "arm-trusted-firmware";',
|
|
'load = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'],
|
|
'entry = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'],
|
|
'compression = "none";',
|
|
]
|
|
loadables.insert(0, "atf")
|
|
its_field_check += [
|
|
'default = "conf";',
|
|
'description = "Boot with signed U-Boot FIT";',
|
|
'loadables = "%s";' % '", "'.join(loadables),
|
|
'fdt = "fdt";',
|
|
]
|
|
return its_field_check
|
|
|
|
def _get_req_sigvalues_config(self, bb_vars):
|
|
# COnfigurations are not signed by uboot-sign
|
|
return {}
|
|
|
|
def _get_req_sigvalues_image(self, bb_vars):
|
|
if bb_vars['SPL_SIGN_ENABLE'] != "1":
|
|
return {}
|
|
req_sigvalues_image = {
|
|
'algo': '"%s,%s"' % (bb_vars['UBOOT_FIT_HASH_ALG'], bb_vars['UBOOT_FIT_SIGN_ALG']),
|
|
'key-name-hint': '"%s"' % bb_vars['SPL_SIGN_KEYNAME'],
|
|
}
|
|
return req_sigvalues_image
|
|
|
|
def _get_req_sections(self, bb_vars):
|
|
"""Generate the expected output of dumpimage for beaglebone targets
|
|
|
|
The dict generated by this function is supposed to be compared against
|
|
the dict which is generated by the _dump_fitimage function.
|
|
"""
|
|
loadables = ['uboot']
|
|
req_sections = {
|
|
"uboot": {
|
|
"Type": "Standalone Program",
|
|
"Load Address": bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'],
|
|
"Entry Point": bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'],
|
|
},
|
|
"fdt": {
|
|
"Type": "Flat Device Tree",
|
|
}
|
|
}
|
|
if bb_vars['UBOOT_FIT_TEE'] == "1":
|
|
loadables.insert(0, "tee")
|
|
req_sections['tee'] = {
|
|
"Type": "Trusted Execution Environment Image",
|
|
# "Load Address": bb_vars['UBOOT_FIT_TEE_LOADADDRESS'], not printed by mkimage?
|
|
# "Entry Point": bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'], not printed by mkimage?
|
|
}
|
|
if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
|
|
loadables.insert(0, "atf")
|
|
req_sections['atf'] = {
|
|
"Type": "Firmware",
|
|
"Load Address": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'],
|
|
# "Entry Point": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'], not printed by mkimage?
|
|
}
|
|
req_sections["conf"] = {
|
|
"Kernel": "unavailable",
|
|
"FDT": "fdt",
|
|
"Loadables": ','.join(loadables),
|
|
}
|
|
|
|
# Add signing related properties if needed
|
|
uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG']
|
|
uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG']
|
|
spl_sign_enable = bb_vars['SPL_SIGN_ENABLE']
|
|
spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME']
|
|
num_signatures = 0
|
|
if spl_sign_enable == "1":
|
|
for section in req_sections:
|
|
if not section.startswith('conf'):
|
|
req_sections[section]['Sign algo'] = "%s,%s:%s" % \
|
|
(uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname)
|
|
num_signatures += 1
|
|
return (req_sections, num_signatures)
|
|
|
|
def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
|
|
if bb_vars['UBOOT_FITIMAGE_ENABLE'] == '1' and bb_vars['SPL_SIGN_ENABLE'] == "1":
|
|
self.logger.debug("Verifying signatures in the FIT image")
|
|
else:
|
|
self.logger.debug("FIT image is not signed. Signature verification is not needed.")
|
|
return
|
|
|
|
uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG']
|
|
uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG']
|
|
spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME']
|
|
fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[uboot_fit_sign_alg]
|
|
for section, values in sections.items():
|
|
# Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1")
|
|
if section.startswith("conf"):
|
|
# uboot-sign does not sign configuration nodes
|
|
pass
|
|
else:
|
|
# uboot-sign does not add hash nodes, only image signatures
|
|
sign_algo = values.get('Sign algo', None)
|
|
req_sign_algo = "%s,%s:%s" % (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname)
|
|
self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
|
|
sign_value = values.get('Sign value', None)
|
|
self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
|
|
|
|
# Search for the string passed to mkimage in each signed section of the FIT image.
|
|
# Looks like mkimage supports to add a comment but does not support to read it back.
|
|
a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['SPL_MKIMAGE_SIGN_ARGS'])
|
|
self.logger.debug("a_comment: %s" % a_comment)
|
|
if a_comment:
|
|
found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment)
|
|
self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." %
|
|
(num_signatures, a_comment))
|
|
|
|
def _check_kernel_dtb(self, bb_vars):
|
|
"""
|
|
Check if the device-tree from U-Boot has the kernel public key(s).
|
|
|
|
The concat_dtb function of the uboot-sign.bbclass injects the public keys
|
|
which are required for verifying the kernel at run-time into the DTB from
|
|
U-Boot. The following example is from a build with FIT_SIGN_INDIVIDUAL
|
|
set to "1". If it is set to "0" the key-the-kernel-image-key node is not
|
|
present.
|
|
/ {
|
|
...
|
|
signature {
|
|
key-the-kernel-image-key {
|
|
required = "image";
|
|
algo = "sha256,rsa2048";
|
|
...
|
|
};
|
|
key-the-kernel-config-key {
|
|
required = "conf";
|
|
algo = "sha256,rsa2048";
|
|
...
|
|
};
|
|
};
|
|
"""
|
|
# Setup u-boot-tools-native
|
|
dtc_bindir = FitImageTestCase._setup_native('dtc-native')
|
|
|
|
# Check if 1 or 2 signature sections are in the DTB.
|
|
uboot_dtb_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], bb_vars['UBOOT_DTB_IMAGE'])
|
|
algo = "%s,%s" % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG'])
|
|
if bb_vars['FIT_SIGN_INDIVIDUAL'] == "1":
|
|
uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
|
|
key_dtb_path = "/signature/key-" + uboot_sign_img_keyname
|
|
self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "required", "image")
|
|
self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "algo", algo)
|
|
self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "key-name-hint", uboot_sign_img_keyname)
|
|
|
|
uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
|
|
key_dtb_path = "/signature/key-" + uboot_sign_keyname
|
|
self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "required", "conf")
|
|
self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "algo", algo)
|
|
self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "key-name-hint", uboot_sign_keyname)
|
|
|
|
|
|
def test_uboot_fit_image(self):
|
|
"""
|
|
Summary: Check if Uboot FIT image and Image Tree Source
|
|
(its) are built and the Image Tree Source has the
|
|
correct fields.
|
|
Expected: 1. u-boot-fitImage and u-boot-its can be built
|
|
2. The type, load address, entrypoint address and
|
|
default values of U-boot image are correct in the
|
|
Image Tree Source. Not all the fields are tested,
|
|
only the key fields that wont vary between
|
|
different architectures.
|
|
Product: oe-core
|
|
Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com>
|
|
based on work by Usama Arif <usama.arif@arm.com>
|
|
"""
|
|
config = """
|
|
# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
|
|
MACHINE = "qemuarm"
|
|
UBOOT_MACHINE = "am57xx_evm_defconfig"
|
|
SPL_BINARY = "MLO"
|
|
|
|
# Enable creation of the U-Boot fitImage
|
|
UBOOT_FITIMAGE_ENABLE = "1"
|
|
|
|
# (U-boot) fitImage properties
|
|
UBOOT_LOADADDRESS = "0x80080000"
|
|
UBOOT_ENTRYPOINT = "0x80080000"
|
|
UBOOT_FIT_DESC = "A model description"
|
|
"""
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
self._test_fitimage(bb_vars)
|
|
|
|
|
|
def test_sign_standalone_uboot_fit_image(self):
|
|
"""
|
|
Summary: Check if U-Boot FIT image and Image Tree Source (its) are
|
|
created and signed correctly for the scenario where only
|
|
the U-Boot proper fitImage is being created and signed.
|
|
Expected: 1) U-Boot its and FIT image are built successfully
|
|
2) Scanning the its file indicates signing is enabled
|
|
as requested by SPL_SIGN_ENABLE (using keys generated
|
|
via UBOOT_FIT_GENERATE_KEYS)
|
|
3) Dumping the FIT image indicates signature values
|
|
are present
|
|
4) Examination of the do_uboot_assemble_fitimage
|
|
runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
|
|
and SPL_MKIMAGE_SIGN_ARGS are working as expected.
|
|
Product: oe-core
|
|
Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
|
|
work by Paul Eggleton <paul.eggleton@microsoft.com> and
|
|
Usama Arif <usama.arif@arm.com>
|
|
"""
|
|
config = """
|
|
# There's no U-boot defconfig with CONFIG_FIT_SIGNATURE yet, so we need at
|
|
# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
|
|
MACHINE = "qemuarm"
|
|
UBOOT_MACHINE = "am57xx_evm_defconfig"
|
|
SPL_BINARY = "MLO"
|
|
# Enable creation and signing of the U-Boot fitImage
|
|
UBOOT_FITIMAGE_ENABLE = "1"
|
|
SPL_SIGN_ENABLE = "1"
|
|
SPL_SIGN_KEYNAME = "spl-oe-selftest"
|
|
SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_DTB_BINARY = "u-boot.dtb"
|
|
UBOOT_ENTRYPOINT = "0x80000000"
|
|
UBOOT_LOADADDRESS = "0x80000000"
|
|
UBOOT_DTB_LOADADDRESS = "0x82000000"
|
|
UBOOT_ARCH = "arm"
|
|
SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
|
|
SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
|
|
UBOOT_EXTLINUX = "0"
|
|
UBOOT_FIT_GENERATE_KEYS = "1"
|
|
UBOOT_FIT_HASH_ALG = "sha256"
|
|
"""
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
self._test_fitimage(bb_vars)
|
|
|
|
|
|
def test_sign_cascaded_uboot_fit_image(self):
|
|
"""
|
|
Summary: Check if U-Boot FIT image and Image Tree Source (its) are
|
|
created and signed correctly for the scenario where both
|
|
U-Boot proper and Kernel fitImages are being created and
|
|
signed.
|
|
Expected: 1) U-Boot its and FIT image are built successfully
|
|
2) Scanning the its file indicates signing is enabled
|
|
as requested by SPL_SIGN_ENABLE (using keys generated
|
|
via UBOOT_FIT_GENERATE_KEYS)
|
|
3) Dumping the FIT image indicates signature values
|
|
are present
|
|
4) Examination of the do_uboot_assemble_fitimage that
|
|
UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and SPL_MKIMAGE_SIGN_ARGS
|
|
are working as expected.
|
|
Product: oe-core
|
|
Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
|
|
work by Paul Eggleton <paul.eggleton@microsoft.com> and
|
|
Usama Arif <usama.arif@arm.com>
|
|
"""
|
|
config = """
|
|
# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
|
|
# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
|
|
MACHINE = "qemuarm"
|
|
UBOOT_MACHINE = "am57xx_evm_defconfig"
|
|
SPL_BINARY = "MLO"
|
|
# Enable creation and signing of the U-Boot fitImage
|
|
UBOOT_FITIMAGE_ENABLE = "1"
|
|
SPL_SIGN_ENABLE = "1"
|
|
SPL_SIGN_KEYNAME = "spl-cascaded-oe-selftest"
|
|
SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_DTB_BINARY = "u-boot.dtb"
|
|
UBOOT_ENTRYPOINT = "0x80000000"
|
|
UBOOT_LOADADDRESS = "0x80000000"
|
|
UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
|
|
UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
|
|
UBOOT_DTB_LOADADDRESS = "0x82000000"
|
|
UBOOT_ARCH = "arm"
|
|
SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
|
|
SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
|
|
UBOOT_EXTLINUX = "0"
|
|
UBOOT_FIT_GENERATE_KEYS = "1"
|
|
UBOOT_FIT_HASH_ALG = "sha256"
|
|
UBOOT_SIGN_ENABLE = "1"
|
|
UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
|
|
"""
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
|
|
self._gen_signing_key(bb_vars)
|
|
self._test_fitimage(bb_vars)
|
|
self._check_kernel_dtb(bb_vars)
|
|
|
|
def test_uboot_atf_tee_fit_image(self):
|
|
"""
|
|
Summary: Check if U-boot FIT image and Image Tree Source
|
|
(its) are built and the Image Tree Source has the
|
|
correct fields.
|
|
Expected: 1. Create atf and tee dummy images
|
|
2. Both u-boot-fitImage and u-boot-its can be built
|
|
3. The os, load address, entrypoint address and
|
|
default values of U-boot, ATF and TEE images are
|
|
correct in the Image Tree Source. Not all the
|
|
fields are tested, only the key fields that wont
|
|
vary between different architectures.
|
|
Product: oe-core
|
|
Author: Jamin Lin <jamin_lin@aspeedtech.com>
|
|
"""
|
|
config = """
|
|
# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
|
|
MACHINE = "qemuarm"
|
|
UBOOT_MACHINE = "am57xx_evm_defconfig"
|
|
SPL_BINARY = "MLO"
|
|
|
|
# Enable creation of the U-Boot fitImage
|
|
UBOOT_FITIMAGE_ENABLE = "1"
|
|
|
|
# (U-boot) fitImage properties
|
|
UBOOT_LOADADDRESS = "0x80080000"
|
|
UBOOT_ENTRYPOINT = "0x80080000"
|
|
UBOOT_FIT_DESC = "A model description"
|
|
|
|
# Enable creation of the TEE fitImage
|
|
UBOOT_FIT_TEE = "1"
|
|
|
|
# TEE fitImage properties
|
|
UBOOT_FIT_TEE_IMAGE = "${TOPDIR}/tee-dummy.bin"
|
|
UBOOT_FIT_TEE_LOADADDRESS = "0x80180000"
|
|
UBOOT_FIT_TEE_ENTRYPOINT = "0x80180000"
|
|
|
|
# Enable creation of the ATF fitImage
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1"
|
|
|
|
# ATF fitImage properties
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin"
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000"
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
|
|
"""
|
|
self.write_config(config)
|
|
|
|
bb_vars = self._fit_get_bb_vars([
|
|
'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE',
|
|
'UBOOT_FIT_TEE_IMAGE',
|
|
])
|
|
|
|
# Create an ATF dummy image
|
|
dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'])
|
|
FitImageTestCase._gen_random_file(dummy_atf)
|
|
|
|
# Create a TEE dummy image
|
|
dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE'])
|
|
FitImageTestCase._gen_random_file(dummy_tee)
|
|
|
|
self._test_fitimage(bb_vars)
|
|
|
|
def test_sign_standalone_uboot_atf_tee_fit_image(self):
|
|
"""
|
|
Summary: Check if U-Boot FIT image and Image Tree Source (its) are
|
|
created and signed correctly for the scenario where only
|
|
the U-Boot proper fitImage is being created and signed.
|
|
Expected: 1. Create atf and tee dummy images
|
|
2. U-Boot its and FIT image are built successfully
|
|
3. Scanning the its file indicates signing is enabled
|
|
as requested by SPL_SIGN_ENABLE (using keys generated
|
|
via UBOOT_FIT_GENERATE_KEYS)
|
|
4. Dumping the FIT image indicates signature values
|
|
are present
|
|
5. Examination of the do_uboot_assemble_fitimage
|
|
runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
|
|
and SPL_MKIMAGE_SIGN_ARGS are working as expected.
|
|
Product: oe-core
|
|
Author: Jamin Lin <jamin_lin@aspeedtech.com>
|
|
"""
|
|
config = """
|
|
# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
|
|
# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
|
|
MACHINE = "qemuarm"
|
|
UBOOT_MACHINE = "am57xx_evm_defconfig"
|
|
SPL_BINARY = "MLO"
|
|
# Enable creation and signing of the U-Boot fitImage
|
|
UBOOT_FITIMAGE_ENABLE = "1"
|
|
SPL_SIGN_ENABLE = "1"
|
|
SPL_SIGN_KEYNAME = "spl-oe-selftest"
|
|
SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_DTB_BINARY = "u-boot.dtb"
|
|
UBOOT_ENTRYPOINT = "0x80000000"
|
|
UBOOT_LOADADDRESS = "0x80000000"
|
|
UBOOT_ARCH = "arm"
|
|
SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
|
|
SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot ATF TEE comment'"
|
|
UBOOT_EXTLINUX = "0"
|
|
UBOOT_FIT_GENERATE_KEYS = "1"
|
|
UBOOT_FIT_HASH_ALG = "sha256"
|
|
|
|
# Enable creation of the TEE fitImage
|
|
UBOOT_FIT_TEE = "1"
|
|
|
|
# TEE fitImage properties
|
|
UBOOT_FIT_TEE_IMAGE = "${TOPDIR}/tee-dummy.bin"
|
|
UBOOT_FIT_TEE_LOADADDRESS = "0x80180000"
|
|
UBOOT_FIT_TEE_ENTRYPOINT = "0x80180000"
|
|
|
|
# Enable creation of the ATF fitImage
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1"
|
|
|
|
# ATF fitImage properties
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin"
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000"
|
|
UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
|
|
"""
|
|
self.write_config(config)
|
|
|
|
bb_vars = self._fit_get_bb_vars([
|
|
'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE',
|
|
'UBOOT_FIT_TEE_IMAGE',
|
|
])
|
|
|
|
# Create an ATF dummy image
|
|
dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'])
|
|
FitImageTestCase._gen_random_file(dummy_atf)
|
|
|
|
# Create a TEE dummy image
|
|
dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE'])
|
|
FitImageTestCase._gen_random_file(dummy_tee)
|
|
|
|
self._test_fitimage(bb_vars)
|
|
|
|
|
|
def test_sign_uboot_kernel_individual(self):
|
|
"""
|
|
Summary: Check if the device-tree from U-Boot has two public keys
|
|
for verifying the kernel FIT image created by the
|
|
kernel-fitimage.bbclass included.
|
|
This test sets: FIT_SIGN_INDIVIDUAL = "1"
|
|
Expected: There must be two signature nodes. One is required for
|
|
the individual image nodes, the other is required for the
|
|
verification of the configuration section.
|
|
"""
|
|
config = """
|
|
# Enable creation of fitImage
|
|
MACHINE = "beaglebone-yocto"
|
|
UBOOT_SIGN_ENABLE = "1"
|
|
UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
|
|
UBOOT_SIGN_KEYNAME = "the-kernel-config-key"
|
|
UBOOT_SIGN_IMG_KEYNAME = "the-kernel-image-key"
|
|
UBOOT_MKIMAGE_DTCOPTS="-I dts -O dtb -p 2000"
|
|
FIT_SIGN_INDIVIDUAL = "1"
|
|
"""
|
|
self.write_config(config)
|
|
bb_vars = self._fit_get_bb_vars()
|
|
self._gen_signing_key(bb_vars)
|
|
|
|
bitbake(UBootFitImageTests.BOOTLOADER_RECIPE)
|
|
|
|
# Just check the DTB of u-boot since there is no u-boot FIT image
|
|
self._check_kernel_dtb(bb_vars)
|