.SY mkimage
.RI [ option\~ .\|.\|.\&]
.BI \-f\~ image-tree-source-file\c
-.RB | auto
+.RB | auto\c
+.RB | auto-conf
.I image-file-name
.YS
.
for details on using external data.
.
.TP
-\fB\-f \fIimage-tree-source-file\fR | \fBauto
+\fB\-f \fIimage-tree-source-file\fR | \fBauto\fR | \fBauto-conf
.TQ
-\fB\-\-fit \fIimage-tree-source-file\fR | \fBauto
+\fB\-\-fit \fIimage-tree-source-file\fR | \fBauto\fR | \fBauto-conf
Image tree source file that describes the structure and contents of the
FIT image.
.IP
options may be used to specify the image to include in the FIT and its
attributes. No
.I image-tree-source-file
-is required.
+is required. The
+.BR \-g ,
+.BR \-o ,
+and
+.B \-k
+or
+.B \-G
+options may be used to get \(oqimages\(cq signed subnodes in the generated
+auto FIT. Instead, to get \(oqconfigurations\(cq signed subnodes and
+\(oqimages\(cq hashed subnodes, pass
+.BR "\-f auto-conf".
+In this case
+.BR \-g ,
+.BR \-o ,
+and
+.B \-k
+or
+.B \-G
+are mandatory options.
.
.TP
.B \-F
necessary when embedding it into another device tree using
.BR \-K .
.I name
-defaults to the value of the signature node's \(oqkey-name-hint\(cq property,
-but may be overridden using
-.BR \-g .
+is the value of the signature node's \(oqkey-name-hint\(cq property.
.
.TP
.BI \-G " key-file"
.TQ
.BI \-\-key\-file " key-file"
Specifies the private key file to use when signing. This option may be used
-instead of \-k.
+instead of \-k. Useful when the private key file basename does not match
+\(oqkey-name-hint\(cq value. But note that it may lead to unexpected results
+when used together with -K and/or -k options.
.
.TP
.BI \-K " key-destination"
.BI \-g " key-name-hint"
.TQ
.BI \-\-key\-name\-hint " key-name-hint"
-Overrides the signature node's \(oqkey-name-hint\(cq property. This is
-especially useful when signing an image with
-.BR "\-f auto" .
-This is the
-.I name
-part of the key. The directory part is set by
-.BR \-k .
-This option also indicates that the images included in the FIT should be signed.
-If this option is specified, then
+Specifies the value of signature node \(oqkey-name-hint\(cq property for
+an automatically generated FIT image. It makes sense only when used with
+.B "\-f auto"
+or
+.BR "\-f auto-conf".
+This option also indicates that the images or configurations included in
+the FIT should be signed. If this option is specified, then
.B \-o
must be specified as well.
.
.TP
-.BI \-o " crypto" , checksum
+.BI \-o " checksum" , crypto
.TQ
-.BI \-\-algo " crypto" , checksum
-Specifies the algorithm to be used for signing a FIT image. The default is
-taken from the signature node's \(oqalgo\(cq property.
+.BI \-\-algo " checksum" , crypto
+Specifies the algorithm to be used for signing a FIT image, overriding value
+taken from the signature node \(oqalgo\(cq property in the
+.IR image-tree-source-file .
+It is mandatory for automatically generated FIT.
+.IP
The valid values for
-.I crypto
+.I checksum
are:
.RS
.IP
.TS
lb.
-rsa2048
-rsa3072
-rsa4096
-ecdsa256
+sha1
+sha256
+sha384
+sha512
.TE
.RE
.IP
The valid values for
-.I checksum
-are
+.I crypto
+are:
.RS
.IP
.TS
lb.
-sha1
-sha256
-sha384
-sha512
+rsa2048
+rsa3072
+rsa4096
+ecdsa256
.TE
.RE
.
.B \-r
.TQ
.B \-\-key\-required
-Specifies that keys used to sign the FIT are required. This means that they
-must be verified for the image to boot. Without this option, the verification
-will be optional (useful for testing but not for release).
+Specifies that keys used to sign the FIT are required. This means that images
+or configurations signatures must be verified before using them (i.e. to
+boot). Without this option, the verification will be optional (useful for
+testing but not for release). It makes sense only when used with
+.BR \-K.
+When both, images and configurations, are signed, \(oqrequired\(cq property
+value will be "conf".
.
.TP
.BI \-N " engine"
.EE
.RE
.P
-Add public keys to u\-boot.dtb without needing a FIT to sign. This will also
+Add public key to u\-boot.dtb without needing a FIT to sign. This will also
create a FIT containing an images node with no data named unused.itb.
.RS
.P
.EE
.RE
.P
+Add public key with required = "conf" property to u\-boot.dtb without needing
+a FIT to sign. This will also create a useless FIT named unused.itb.
+.RS
+.P
+.EX
+\fBmkimage \-f auto-conf \-d /dev/null \-k /public/signing\-keys \-g dev \\
+ \-o sha256,rsa2048 \-K u\-boot.dtb -r unused.itb
+.EE
+.RE
+.P
Update an existing FIT image, signing it with additional keys.
Add corresponding public keys into u\-boot.dtb. This will resign all images
with keys that are available in the new directory. Images that request signing
\-d vmlinuz \-k /secret/signing\-keys \-g dev \-o sha256,rsa2048 kernel.itb
.EE
.RE
+.P
+Create a FIT image containing a kernel and some device tree files, signing
+each configuration, using automatic mode. Moreover, the public key needed to
+verify signatures is added to u\-boot.dtb with required = "conf" property.
+.RS
+.P
+.EX
+\fBmkimage \-f auto-conf \-A arm \-O linux \-T kernel \-C none \-a 43e00000 \\
+ \-e 0 \-d vmlinuz \-b /path/to/file\-1.dtb \-b /path/to/file\-2.dtb \\
+ \-k /folder/with/signing\-keys \-g dev \-o sha256,rsa2048 \\
+ \-K u\-boot.dtb -r kernel.itb
+.EE
+.RE
.
.SH SEE ALSO
.BR dtc (1),
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2022 Massimo Pegorer
+
+"""
+Test that mkimage generates auto-FIT with signatures and/or hashes as expected.
+
+The mkimage tool can create auto generated (i.e. without an ITS file
+provided as input) FIT in three different flavours: with crc32 checksums
+of 'images' subnodes; with signatures of 'images' subnodes; with sha1
+hashes of 'images' subnodes and signatures of 'configurations' subnodes.
+This test verifies that auto-FIT are generated as expected, in all of
+the three flavours, including check of hashes and signatures (except for
+configurations ones).
+
+The test does not run the sandbox. It only checks the host tool mkimage.
+"""
+
+import os
+import pytest
+import u_boot_utils as util
+import binascii
+from Cryptodome.Hash import SHA1
+from Cryptodome.Hash import SHA256
+from Cryptodome.PublicKey import RSA
+from Cryptodome.Signature import pkcs1_15
+
+class SignedFitHelper(object):
+ """Helper to manipulate a FIT with signed/hashed images/configs."""
+ def __init__(self, cons, file_name):
+ self.fit = file_name
+ self.cons = cons
+ self.images_nodes = set()
+ self.confgs_nodes = set()
+
+ def __fdt_list(self, path):
+ return util.run_and_log(self.cons,
+ f'fdtget -l {self.fit} {path}')
+
+ def __fdt_get_string(self, node, prop):
+ return util.run_and_log(self.cons,
+ f'fdtget -ts {self.fit} {node} {prop}')
+
+ def __fdt_get_binary(self, node, prop):
+ numbers = util.run_and_log(self.cons,
+ f'fdtget -tbi {self.fit} {node} {prop}')
+
+ bignum = bytearray()
+ for little_num in numbers.split():
+ bignum.append(int(little_num))
+
+ return bignum
+
+ def build_nodes_sets(self):
+ """Fill sets with FIT images and configurations subnodes."""
+ for node in self.__fdt_list('/images').split():
+ subnode = f'/images/{node}'
+ self.images_nodes.add(subnode)
+
+ for node in self.__fdt_list('/configurations').split():
+ subnode = f'/configurations/{node}'
+ self.confgs_nodes.add(subnode)
+
+ return len(self.images_nodes) + len(self.confgs_nodes)
+
+ def check_fit_crc32_images(self):
+ """Test that all images in the set are hashed as expected.
+
+ Each image must have an hash with algo=crc32 and hash value must match
+ the one calculated over image data.
+ """
+ for node in self.images_nodes:
+ algo = self.__fdt_get_string(f'{node}/hash', 'algo')
+ assert algo == "crc32\n", "Missing expected crc32 image hash!"
+
+ raw_crc32 = self.__fdt_get_binary(f'{node}/hash', 'value')
+ raw_bin = self.__fdt_get_binary(node, 'data')
+ assert raw_crc32 == (binascii.crc32(raw_bin) &
+ 0xffffffff).to_bytes(4, 'big'), "Wrong crc32 hash!"
+
+ def check_fit_signed_images(self, key_name, sign_algo, verifier):
+ """Test that all images in the set are signed as expected.
+
+ Each image must have a signature with: key-name-hint matching key_name
+ argument; algo matching sign_algo argument; value matching the one
+ calculated over image data using verifier argument.
+ """
+ for node in self.images_nodes:
+ hint = self.__fdt_get_string(f'{node}/signature', 'key-name-hint')
+ assert hint == key_name + "\n", "Missing expected key name hint!"
+ algo = self.__fdt_get_string(f'{node}/signature', 'algo')
+ assert algo == sign_algo + "\n", "Missing expected signature algo!"
+
+ raw_sig = self.__fdt_get_binary(f'{node}/signature', 'value')
+ raw_bin = self.__fdt_get_binary(node, 'data')
+ verifier.verify(SHA256.new(raw_bin), bytes(raw_sig))
+
+ def check_fit_signed_confgs(self, key_name, sign_algo):
+ """Test that all configs are signed, and images hashed, as expected.
+
+ Each image must have an hash with algo=sha1 and hash value must match
+ the one calculated over image data. Each configuration must have a
+ signature with key-name-hint matching key_name argument and algo
+ matching sign_algo argument.
+ TODO: configurations signature checking.
+ """
+ for node in self.images_nodes:
+ algo = self.__fdt_get_string(f'{node}/hash', 'algo')
+ assert algo == "sha1\n", "Missing expected sha1 image hash!"
+
+ raw_hash = self.__fdt_get_binary(f'{node}/hash', 'value')
+ raw_bin = self.__fdt_get_binary(node, 'data')
+ assert raw_hash == SHA1.new(raw_bin).digest(), "Wrong sha1 hash!"
+
+ for node in self.confgs_nodes:
+ hint = self.__fdt_get_string(f'{node}/signature', 'key-name-hint')
+ assert hint == key_name + "\n", "Missing expected key name hint!"
+ algo = self.__fdt_get_string(f'{node}/signature', 'algo')
+ assert algo == sign_algo + "\n", "Missing expected signature algo!"
+
+
+@pytest.mark.buildconfigspec('fit_signature')
+@pytest.mark.requiredtool('fdtget')
+def test_fit_auto_signed(u_boot_console):
+ """Test that mkimage generates auto-FIT with signatures/hashes as expected.
+
+ The mkimage tool can create auto generated (i.e. without an ITS file
+ provided as input) FIT in three different flavours: with crc32 checksums
+ of 'images' subnodes; with signatures of 'images' subnodes; with sha1
+ hashes of 'images' subnodes and signatures of 'configurations' subnodes.
+ This test verifies that auto-FIT are generated as expected, in all of
+ the three flavours, including check of hashes and signatures (except for
+ configurations ones).
+
+ The test does not run the sandbox. It only checks the host tool mkimage.
+ """
+ cons = u_boot_console
+ mkimage = cons.config.build_dir + '/tools/mkimage'
+ tempdir = os.path.join(cons.config.result_dir, 'auto_fit')
+ os.makedirs(tempdir, exist_ok=True)
+ kernel_file = f'{tempdir}/vmlinuz'
+ dt1_file = f'{tempdir}/dt-1.dtb'
+ dt2_file = f'{tempdir}/dt-2.dtb'
+ key_name = 'sign-key'
+ sign_algo = 'sha256,rsa4096'
+ key_file = f'{tempdir}/{key_name}.key'
+ fit_file = f'{tempdir}/test.fit'
+
+ # Create a fake kernel image and two dtb files with random data
+ with open(kernel_file, 'wb') as fd:
+ fd.write(os.urandom(512))
+
+ with open(dt1_file, 'wb') as fd:
+ fd.write(os.urandom(256))
+
+ with open(dt2_file, 'wb') as fd:
+ fd.write(os.urandom(256))
+
+ # Create 4096 RSA key and write to file to be read by mkimage
+ key = RSA.generate(bits=4096)
+ verifier = pkcs1_15.new(key)
+
+ with open(key_file, 'w') as fd:
+ fd.write(str(key.export_key(format='PEM').decode('ascii')))
+
+ b_args = " -d" + kernel_file + " -b" + dt1_file + " -b" + dt2_file
+ s_args = " -k" + tempdir + " -g" + key_name + " -o" + sign_algo
+
+ # 1 - Create auto FIT with images crc32 checksum, and verify it
+ util.run_and_log(cons, mkimage + ' -fauto' + b_args + " " + fit_file)
+
+ fit = SignedFitHelper(cons, fit_file)
+ if fit.build_nodes_sets() == 0:
+ raise ValueError('FIT-1 has no "/image" nor "/configuration" nodes')
+
+ fit.check_fit_crc32_images()
+
+ # 2 - Create auto FIT with signed images, and verify it
+ util.run_and_log(cons, mkimage + ' -fauto' + b_args + s_args + " " +
+ fit_file)
+
+ fit = SignedFitHelper(cons, fit_file)
+ if fit.build_nodes_sets() == 0:
+ raise ValueError('FIT-2 has no "/image" nor "/configuration" nodes')
+
+ fit.check_fit_signed_images(key_name, sign_algo, verifier)
+
+ # 3 - Create auto FIT with signed configs and hashed images, and verify it
+ util.run_and_log(cons, mkimage + ' -fauto-conf' + b_args + s_args + " " +
+ fit_file)
+
+ fit = SignedFitHelper(cons, fit_file)
+ if fit.build_nodes_sets() == 0:
+ raise ValueError('FIT-3 has no "/image" nor "/configuration" nodes')
+
+ fit.check_fit_signed_confgs(key_name, sign_algo)
}
/**
- * add_hash_node() - Add a hash or signature node
+ * fit_add_hash_or_sign() - Add a hash or signature node
*
* @params: Image parameters
* @fdt: Device tree to add to (in sequential-write mode)
+ * @is_images_subnode: true to add hash even if key name hint is provided
*
- * If there is a key name hint, try to sign the images. Otherwise, just add a
- * CRC.
- *
- * Return: 0 on success, or -1 on failure
+ * If do_add_hash is false (default) and there is a key name hint, try to add
+ * a sign node to parent. Otherwise, just add a CRC. Rationale: if conf have
+ * to be signed, image/dt have to be hashed even if there is a key name hint.
*/
-static int add_hash_node(struct image_tool_params *params, void *fdt)
+static void fit_add_hash_or_sign(struct image_tool_params *params, void *fdt,
+ bool is_images_subnode)
{
- if (params->keyname) {
- if (!params->algo_name) {
- fprintf(stderr,
- "%s: Algorithm name must be specified\n",
- params->cmdname);
- return -1;
+ const char *hash_algo = "crc32";
+ bool do_hash = false;
+ bool do_sign = false;
+
+ switch (params->auto_fit) {
+ case AF_OFF:
+ break;
+ case AF_HASHED_IMG:
+ do_hash = is_images_subnode;
+ break;
+ case AF_SIGNED_IMG:
+ do_sign = is_images_subnode;
+ break;
+ case AF_SIGNED_CONF:
+ if (is_images_subnode) {
+ do_hash = true;
+ hash_algo = "sha1";
+ } else {
+ do_sign = true;
}
+ break;
+ default:
+ fprintf(stderr,
+ "%s: Unsupported auto FIT mode %u\n",
+ params->cmdname, params->auto_fit);
+ break;
+ }
+
+ if (do_hash) {
+ fdt_begin_node(fdt, FIT_HASH_NODENAME);
+ fdt_property_string(fdt, FIT_ALGO_PROP, hash_algo);
+ fdt_end_node(fdt);
+ }
- fdt_begin_node(fdt, "signature-1");
+ if (do_sign) {
+ fdt_begin_node(fdt, FIT_SIG_NODENAME);
fdt_property_string(fdt, FIT_ALGO_PROP, params->algo_name);
fdt_property_string(fdt, FIT_KEY_HINT, params->keyname);
- } else {
- fdt_begin_node(fdt, "hash-1");
- fdt_property_string(fdt, FIT_ALGO_PROP, "crc32");
+ fdt_end_node(fdt);
}
-
- fdt_end_node(fdt);
- return 0;
}
/**
ret = fdt_property_file(params, fdt, FIT_DATA_PROP, params->datafile);
if (ret)
return ret;
- ret = add_hash_node(params, fdt);
- if (ret)
- return ret;
+ fit_add_hash_or_sign(params, fdt, true);
fdt_end_node(fdt);
/* Now the device tree files if available */
genimg_get_arch_short_name(params->arch));
fdt_property_string(fdt, FIT_COMP_PROP,
genimg_get_comp_short_name(IH_COMP_NONE));
- ret = add_hash_node(params, fdt);
+ fit_add_hash_or_sign(params, fdt, true);
if (ret)
return ret;
fdt_end_node(fdt);
params->fit_ramdisk);
if (ret)
return ret;
- ret = add_hash_node(params, fdt);
+ fit_add_hash_or_sign(params, fdt, true);
if (ret)
return ret;
fdt_end_node(fdt);
snprintf(str, sizeof(str), FIT_FDT_PROP "-%d", upto);
fdt_property_string(fdt, FIT_FDT_PROP, str);
+ fit_add_hash_or_sign(params, fdt, false);
fdt_end_node(fdt);
}
if (params->fit_ramdisk)
fdt_property_string(fdt, FIT_RAMDISK_PROP,
FIT_RAMDISK_PROP "-1");
+ fit_add_hash_or_sign(params, fdt, false);
fdt_end_node(fdt);
}
sprintf (tmpfile, "%s%s", params->imagefile, MKIMAGE_TMPFILE_SUFFIX);
/* We either compile the source file, or use the existing FIT image */
- if (params->auto_its) {
+ if (params->auto_fit) {
if (fit_build(params, tmpfile)) {
fprintf(stderr, "%s: failed to build FIT\n",
params->cmdname);
static int fit_check_params(struct image_tool_params *params)
{
- if (params->auto_its)
+ if (params->auto_fit)
return 0;
return ((params->dflag && params->fflag) ||
(params->fflag && params->lflag) ||
const char *fname;
};
+/* FIT auto generation modes */
+enum af_mode {
+ AF_OFF = 0, /* Needs .its or existing FIT to be provided */
+ AF_HASHED_IMG, /* Auto FIT with crc32 hashed images subnodes */
+ AF_SIGNED_IMG, /* Auto FIT with signed images subnodes */
+ AF_SIGNED_CONF, /* Auto FIT with sha1 images and signed configs */
+};
+
/*
* This structure defines all such variables those are initialized by
* mkimage and dumpimage main core and need to be referred by image
int require_keys; /* 1 to mark signing keys as 'required' */
int file_size; /* Total size of output file */
int orig_file_size; /* Original size for file before padding */
- bool auto_its; /* Automatically create the .its file */
+ enum af_mode auto_fit; /* Automatically create the FIT */
int fit_image_type; /* Image type to put into the FIT */
char *fit_ramdisk; /* Ramdisk file to include */
struct content_info *content_head; /* List of files to include */
" -v ==> verbose\n",
params.cmdname);
fprintf(stderr,
- " %s [-D dtc_options] [-f fit-image.its|-f auto|-F] [-b <dtb> [-b <dtb>]] [-E] [-B size] [-i <ramdisk.cpio.gz>] fit-image\n"
+ " %s [-D dtc_options] [-f fit-image.its|-f auto|-f auto-conf|-F] [-b <dtb> [-b <dtb>]] [-E] [-B size] [-i <ramdisk.cpio.gz>] fit-image\n"
" <dtb> file is used with -f auto, it may occur multiple times.\n",
params.cmdname);
fprintf(stderr,
break;
case 'f':
datafile = optarg;
- params.auto_its = !strcmp(datafile, "auto");
+ if (!strcmp(datafile, "auto"))
+ params.auto_fit = AF_HASHED_IMG;
+ else if (!strcmp(datafile, "auto-conf"))
+ params.auto_fit = AF_SIGNED_CONF;
/* fallthrough */
case 'F':
/*
break;
case 'g':
params.keyname = optarg;
+ break;
case 'G':
params.keyfile = optarg;
break;
if (optind < argc)
params.imagefile = argv[optind];
+ if (params.auto_fit == AF_SIGNED_CONF) {
+ if (!params.keyname || !params.algo_name)
+ usage("Missing key/algo for auto-FIT with signed configs (use -g -o)");
+ } else if (params.auto_fit == AF_HASHED_IMG && params.keyname) {
+ params.auto_fit = AF_SIGNED_IMG;
+ if (!params.algo_name)
+ usage("Missing algorithm for auto-FIT with signed images (use -g)");
+ }
+
/*
* For auto-generated FIT images we need to know the image type to put
* in the FIT, which is separate from the file's image type (which
*/
if (params.type == IH_TYPE_FLATDT) {
params.fit_image_type = type ? type : IH_TYPE_KERNEL;
- /* For auto_its, datafile is always 'auto' */
- if (!params.auto_its)
+ /* For auto-FIT, datafile has to be provided with -d */
+ if (!params.auto_fit)
params.datafile = datafile;
else if (!params.datafile)
usage("Missing data file for auto-FIT (use -d)");