This implementation is inspired by U-Boot's FIT support. Instead of using libfdt (which does not exist in barebox), configuration signatures are verified by using a simplified DT parser based on barebox's own code. Currently, only signed configurations with hashed images are supported, as the other variants are less useful for verified boot. Compatible FIT images can be created using U-Boot's mkimage tool. Signed-off-by: Jan Luebbe <jlu@pengutronix.de> Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
585 lines
14 KiB
C
585 lines
14 KiB
C
/*
|
|
* Copyright (C) Jan Lübbe, 2014
|
|
*
|
|
* This code is inspired by the U-Boot FIT image code.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "FIT: " fmt
|
|
#include <common.h>
|
|
#include <init.h>
|
|
#include <boot.h>
|
|
#include <libfile.h>
|
|
#include <fdt.h>
|
|
#include <digest.h>
|
|
#include <of.h>
|
|
#include <fs.h>
|
|
#include <malloc.h>
|
|
#include <linux/ctype.h>
|
|
#include <asm/byteorder.h>
|
|
#include <errno.h>
|
|
#include <linux/err.h>
|
|
#include <stringlist.h>
|
|
#include <rsa.h>
|
|
#include <image-fit.h>
|
|
|
|
#define FDT_MAX_DEPTH 32
|
|
#define FDT_MAX_PATH_LEN 200
|
|
|
|
#define CHECK_LEVEL_NONE 0
|
|
#define CHECK_LEVEL_HASH 1
|
|
#define CHECK_LEVEL_SIG 2
|
|
#define CHECK_LEVEL_MAX 3
|
|
|
|
static uint32_t dt_struct_advance(struct fdt_header *f, uint32_t dt, int size)
|
|
{
|
|
dt += size;
|
|
dt = ALIGN(dt, 4);
|
|
|
|
if (dt > f->off_dt_struct + f->size_dt_struct)
|
|
return 0;
|
|
|
|
return dt;
|
|
}
|
|
|
|
static char *dt_string(struct fdt_header *f, char *strstart, uint32_t ofs)
|
|
{
|
|
if (ofs > f->size_dt_strings)
|
|
return NULL;
|
|
else
|
|
return strstart + ofs;
|
|
}
|
|
|
|
static int of_read_string_list(struct device_node *np, const char *name, struct string_list *sl)
|
|
{
|
|
struct property *prop;
|
|
const char *s;
|
|
|
|
of_property_for_each_string(np, name, prop, s) {
|
|
string_list_add(sl, s);
|
|
}
|
|
|
|
return prop ? 0 : -EINVAL;
|
|
}
|
|
|
|
static int fit_digest(void *fit, struct digest *digest,
|
|
struct string_list *inc_nodes, struct string_list *exc_props,
|
|
uint32_t hashed_strings_start, uint32_t hashed_strings_size)
|
|
{
|
|
struct fdt_header *fdt = fit;
|
|
uint32_t dt_struct;
|
|
void *dt_strings;
|
|
struct fdt_header f = {};
|
|
int stack[FDT_MAX_DEPTH];
|
|
char path[FDT_MAX_PATH_LEN];
|
|
char *end;
|
|
uint32_t tag;
|
|
int start = -1;
|
|
int depth = -1;
|
|
int want = 0;
|
|
|
|
f.totalsize = fdt32_to_cpu(fdt->totalsize);
|
|
f.off_dt_struct = fdt32_to_cpu(fdt->off_dt_struct);
|
|
f.size_dt_struct = fdt32_to_cpu(fdt->size_dt_struct);
|
|
f.off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings);
|
|
f.size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings);
|
|
|
|
if (hashed_strings_start > f.size_dt_strings ||
|
|
hashed_strings_size > f.size_dt_strings ||
|
|
hashed_strings_start + hashed_strings_size > f.size_dt_strings) {
|
|
pr_err("%s: hashed-strings too large\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dt_struct = f.off_dt_struct;
|
|
dt_strings = (void *)fdt + f.off_dt_strings;
|
|
|
|
end = path;
|
|
*end = '\0';
|
|
|
|
do {
|
|
const struct fdt_property *fdt_prop;
|
|
const struct fdt_node_header *fnh;
|
|
const char *name;
|
|
int include = 0;
|
|
int stop_at = 0;
|
|
int offset = dt_struct;
|
|
int maxlen, len;
|
|
|
|
tag = be32_to_cpu(*(uint32_t *)(fit + dt_struct));
|
|
|
|
switch (tag) {
|
|
case FDT_BEGIN_NODE:
|
|
fnh = fit + dt_struct;
|
|
name = fnh->name;
|
|
maxlen = (unsigned long)fdt + f.off_dt_struct +
|
|
f.size_dt_struct - (unsigned long)name;
|
|
|
|
len = strnlen(name, maxlen + 1);
|
|
if (len > maxlen)
|
|
return -ESPIPE;
|
|
|
|
dt_struct = dt_struct_advance(&f, dt_struct,
|
|
sizeof(struct fdt_node_header) + len + 1);
|
|
|
|
depth++;
|
|
if (depth == FDT_MAX_DEPTH)
|
|
return -ESPIPE;
|
|
if (end - path + 2 + len >= FDT_MAX_PATH_LEN)
|
|
return -ESPIPE;
|
|
if (end != path + 1)
|
|
*end++ = '/';
|
|
strcpy(end, name);
|
|
end += len;
|
|
stack[depth] = want;
|
|
if (want == 1)
|
|
stop_at = offset;
|
|
if (string_list_contains(inc_nodes, path))
|
|
want = 2;
|
|
else if (want)
|
|
want--;
|
|
else
|
|
stop_at = offset;
|
|
include = want;
|
|
|
|
break;
|
|
|
|
case FDT_END_NODE:
|
|
dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE);
|
|
|
|
include = want;
|
|
want = stack[depth--];
|
|
while (end > path && *--end != '/')
|
|
;
|
|
*end = '\0';
|
|
|
|
break;
|
|
|
|
case FDT_PROP:
|
|
fdt_prop = fit + dt_struct;
|
|
len = fdt32_to_cpu(fdt_prop->len);
|
|
|
|
name = dt_string(&f, dt_strings, fdt32_to_cpu(fdt_prop->nameoff));
|
|
if (!name)
|
|
return -ESPIPE;
|
|
|
|
dt_struct = dt_struct_advance(&f, dt_struct,
|
|
sizeof(struct fdt_property) + len);
|
|
|
|
include = want >= 2;
|
|
stop_at = offset;
|
|
if (string_list_contains(exc_props, name))
|
|
include = 0;
|
|
|
|
break;
|
|
|
|
case FDT_NOP:
|
|
dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE);
|
|
|
|
include = want >= 2;
|
|
stop_at = offset;
|
|
|
|
break;
|
|
|
|
case FDT_END:
|
|
dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE);
|
|
|
|
include = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
pr_err("%s: Unknown tag 0x%08X\n", __func__, tag);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!dt_struct)
|
|
return -ESPIPE;
|
|
|
|
pr_debug("%s: include %d, want %d, offset 0x%x, len 0x%x\n",
|
|
path, include, want, offset, dt_struct-offset);
|
|
|
|
if (include && start == -1)
|
|
start = offset;
|
|
|
|
if (!include && start != -1) {
|
|
pr_debug("region: 0x%p+0x%x\n", fit + start, offset - start);
|
|
digest_update(digest, fit + start, offset - start);
|
|
start = -1;
|
|
}
|
|
} while (tag != FDT_END);
|
|
|
|
pr_debug("region: 0x%p+0x%x\n", fit + start, dt_struct - start);
|
|
digest_update(digest, fit + start, dt_struct - start);
|
|
|
|
pr_debug("strings: 0x%p+0x%x\n", dt_strings+hashed_strings_start, hashed_strings_size);
|
|
digest_update(digest, dt_strings + hashed_strings_start, hashed_strings_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The consistency of the FTD structure was already checked by of_unflatten_dtb()
|
|
*/
|
|
static int fit_verify_signature(struct device_node *sig_node, void *fit)
|
|
{
|
|
uint32_t hashed_strings_start, hashed_strings_size;
|
|
struct string_list inc_nodes, exc_props;
|
|
struct rsa_public_key key = {};
|
|
struct digest *digest;
|
|
int sig_len;
|
|
const char *algo_name, *key_name, *sig_value;
|
|
char *key_path;
|
|
struct device_node *key_node;
|
|
enum hash_algo algo;
|
|
void *hash;
|
|
int ret;
|
|
|
|
if (of_property_read_string(sig_node, "algo", &algo_name)) {
|
|
pr_err("algo not found\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (strcmp(algo_name, "sha1,rsa2048") == 0) {
|
|
algo = HASH_ALGO_SHA1;
|
|
} else if (strcmp(algo_name, "sha256,rsa4096") == 0) {
|
|
algo = HASH_ALGO_SHA256;
|
|
} else {
|
|
pr_err("unknown algo %s\n", algo_name);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
digest = digest_alloc_by_algo(algo);
|
|
if (!digest) {
|
|
pr_err("unsupported algo %s\n", algo_name);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
sig_value = of_get_property(sig_node, "value", &sig_len);
|
|
if (!sig_value) {
|
|
pr_err("signature value not found in %s\n", sig_node->full_name);
|
|
ret = -EINVAL;
|
|
goto out_free_digest;
|
|
}
|
|
|
|
if (of_property_read_string(sig_node, "key-name-hint", &key_name)) {
|
|
pr_err("key name not found in %s\n", sig_node->full_name);
|
|
ret = -EINVAL;
|
|
goto out_free_digest;
|
|
}
|
|
key_path = xasprintf("/signature/key-%s", key_name);
|
|
key_node = of_find_node_by_path(key_path);
|
|
free(key_path);
|
|
if (!key_node) {
|
|
pr_info("failed to find key node\n");
|
|
ret = -ENOENT;
|
|
goto out_free_digest;
|
|
}
|
|
|
|
ret = rsa_of_read_key(key_node, &key);
|
|
if (ret) {
|
|
pr_info("failed to read key in %s\n", key_node->full_name);
|
|
ret = -ENOENT;
|
|
goto out_free_digest;
|
|
}
|
|
|
|
if (of_property_read_u32_index(sig_node, "hashed-strings", 0, &hashed_strings_start)) {
|
|
pr_err("hashed-strings start not found in %s\n", sig_node->full_name);
|
|
ret = -EINVAL;
|
|
goto out_free_digest;
|
|
}
|
|
if (of_property_read_u32_index(sig_node, "hashed-strings", 1, &hashed_strings_size)) {
|
|
pr_err("hashed-strings size not found in %s\n", sig_node->full_name);
|
|
ret = -EINVAL;
|
|
goto out_free_digest;
|
|
}
|
|
|
|
string_list_init(&inc_nodes);
|
|
string_list_init(&exc_props);
|
|
|
|
if (of_read_string_list(sig_node, "hashed-nodes", &inc_nodes)) {
|
|
pr_err("hashed-nodes property not found in %s\n", sig_node->full_name);
|
|
ret = -EINVAL;
|
|
goto out_sl;
|
|
}
|
|
|
|
string_list_add(&exc_props, "data");
|
|
|
|
digest_init(digest);
|
|
ret = fit_digest(fit, digest, &inc_nodes, &exc_props, hashed_strings_start, hashed_strings_size);
|
|
hash = xzalloc(digest_length(digest));
|
|
digest_final(digest, hash);
|
|
|
|
ret = rsa_verify(&key, sig_value, sig_len, hash, algo);
|
|
if (ret) {
|
|
pr_info("image signature BAD\n");
|
|
ret = -EBADMSG;
|
|
} else {
|
|
pr_info("image signature OK\n");
|
|
ret = 0;
|
|
}
|
|
|
|
free(hash);
|
|
out_sl:
|
|
string_list_free(&inc_nodes);
|
|
string_list_free(&exc_props);
|
|
out_free_digest:
|
|
digest_free(digest);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int fit_verify_hash(struct device_node *hash, const void *data, int data_len)
|
|
{
|
|
struct digest *d;
|
|
const char *algo;
|
|
const char *value_read;
|
|
char *value_calc;
|
|
int hash_len, ret;
|
|
|
|
value_read = of_get_property(hash, "value", &hash_len);
|
|
if (!value_read) {
|
|
pr_err("%s: \"value\" property not found\n", hash->full_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_string(hash, "algo", &algo)) {
|
|
pr_err("%s: \"algo\" property not found\n", hash->full_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
d = digest_alloc(algo);
|
|
if (!d) {
|
|
pr_err("%s: unsupported algo %s\n", hash->full_name, algo);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hash_len != digest_length(d)) {
|
|
pr_err("%s: invalid hash length %d\n", hash->full_name, hash_len);
|
|
ret = -EINVAL;
|
|
goto err_digest_free;
|
|
}
|
|
|
|
value_calc = xmalloc(hash_len);
|
|
|
|
digest_init(d);
|
|
digest_update(d, data, data_len);
|
|
digest_final(d, value_calc);
|
|
|
|
if (memcmp(value_read, value_calc, hash_len)) {
|
|
pr_info("%s: hash BAD\n", hash->full_name);
|
|
ret = -EBADMSG;
|
|
} else {
|
|
pr_info("%s: hash OK\n", hash->full_name);
|
|
ret = 0;
|
|
}
|
|
|
|
free(value_calc);
|
|
|
|
err_digest_free:
|
|
digest_free(d);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fit_open_image(struct fit_handle *handle, const char *unit, const void **outdata,
|
|
unsigned long *outsize)
|
|
{
|
|
struct device_node *image = NULL, *hash;
|
|
const char *type = NULL, *desc= "(no description)";
|
|
const void *data;
|
|
int data_len;
|
|
int ret = 0;
|
|
|
|
image = of_get_child_by_name(handle->root, "images");
|
|
if (!image)
|
|
return -ENOENT;
|
|
|
|
image = of_get_child_by_name(image, unit);
|
|
if (!image)
|
|
return -ENOENT;
|
|
|
|
of_property_read_string(image, "description", &desc);
|
|
pr_info("image '%s': '%s'\n", unit, desc);
|
|
|
|
of_property_read_string(image, "type", &type);
|
|
if (!type) {
|
|
pr_err("No \"type\" property found in %s\n", image->full_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = of_get_property(image, "data", &data_len);
|
|
if (!data) {
|
|
pr_err("data not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (handle->verify > BOOTM_VERIFY_NONE) {
|
|
ret = -EINVAL;
|
|
for_each_child_of_node(image, hash) {
|
|
if (handle->verbose)
|
|
of_print_nodes(hash, 0);
|
|
ret = fit_verify_hash(hash, data, data_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*outdata = data;
|
|
*outsize = data_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fit_open_configuration(struct fit_handle *handle, const char *name)
|
|
{
|
|
struct device_node *conf_node = NULL, *sig_node;
|
|
const char *unit, *desc = "(no description)";
|
|
int ret;
|
|
|
|
conf_node = of_get_child_by_name(handle->root, "configurations");
|
|
if (!conf_node)
|
|
return -ENOENT;
|
|
|
|
if (name) {
|
|
unit = name;
|
|
} else if (of_property_read_string(conf_node, "default", &unit)) {
|
|
unit = "conf@1";
|
|
}
|
|
|
|
conf_node = of_get_child_by_name(conf_node, unit);
|
|
if (!conf_node) {
|
|
pr_err("configuration '%s' not found\n", unit);
|
|
return -ENOENT;
|
|
}
|
|
|
|
of_property_read_string(conf_node, "description", &desc);
|
|
pr_info("configuration '%s': %s\n", unit, desc);
|
|
|
|
if (IS_ENABLED(CONFIG_FITIMAGE_SIGNATURE) &&
|
|
handle->verify == BOOTM_VERIFY_SIGNATURE) {
|
|
ret = -EINVAL;
|
|
for_each_child_of_node(conf_node, sig_node) {
|
|
if (handle->verbose)
|
|
of_print_nodes(sig_node, 0);
|
|
ret = fit_verify_signature(sig_node, handle->fit);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (of_property_read_string(conf_node, "kernel", &unit) == 0) {
|
|
ret = fit_open_image(handle, unit, &handle->kernel, &handle->kernel_size);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (of_property_read_string(conf_node, "fdt", &unit) == 0) {
|
|
ret = fit_open_image(handle, unit, &handle->oftree, &handle->oftree_size);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (of_property_read_string(conf_node, "ramdisk", &unit) == 0) {
|
|
ret = fit_open_image(handle, unit, &handle->initrd, &handle->initrd_size);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct fit_handle *fit_open(const char *filename, const char *config, bool verbose,
|
|
enum bootm_verify verify)
|
|
{
|
|
struct fit_handle *handle = NULL;
|
|
const char *desc = "(no description)";
|
|
int ret;
|
|
|
|
handle = xzalloc(sizeof(struct fit_handle));
|
|
|
|
handle->verbose = verbose;
|
|
|
|
ret = read_file_2(filename, &handle->size, &handle->fit, FILESIZE_MAX);
|
|
if (ret) {
|
|
pr_err("unable to read %s: %s\n", filename, strerror(-ret));
|
|
goto err;
|
|
}
|
|
|
|
handle->root = of_unflatten_dtb(handle->fit);
|
|
if (IS_ERR(handle->root)) {
|
|
ret = PTR_ERR(handle->root);
|
|
goto err;
|
|
}
|
|
|
|
handle->verify = verify;
|
|
|
|
of_property_read_string(handle->root, "description", &desc);
|
|
pr_info("'%s': %s\n", filename, desc);
|
|
|
|
ret = fit_open_configuration(handle, config);
|
|
if (ret)
|
|
goto err;
|
|
|
|
return handle;
|
|
err:
|
|
if (handle->root)
|
|
of_delete_node(handle->root);
|
|
free(handle->fit);
|
|
free(handle);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
void fit_close(struct fit_handle *handle)
|
|
{
|
|
if (handle->root)
|
|
of_delete_node(handle->root);
|
|
if (handle->fit)
|
|
free(handle->fit);
|
|
free(handle);
|
|
}
|
|
|
|
#ifdef CONFIG_SANDBOX
|
|
static int do_bootm_sandbox_fit(struct image_data *data)
|
|
{
|
|
struct fit_handle *handle;
|
|
handle = fit_open(data->os_file, data->os_part, data->verbose);
|
|
if (handle)
|
|
fit_close(handle);
|
|
return 0;
|
|
}
|
|
|
|
static struct image_handler sandbox_fit_handler = {
|
|
.name = "FIT image",
|
|
.bootm = do_bootm_sandbox_fit,
|
|
.filetype = filetype_oftree,
|
|
};
|
|
|
|
static int sandbox_fit_register(void)
|
|
{
|
|
return register_image_handler(&sandbox_fit_handler);
|
|
}
|
|
late_initcall(sandbox_fit_register);
|
|
#endif
|