diff --git a/Documentation/devicetree/bindings/barebox/barebox,state.rst b/Documentation/devicetree/bindings/barebox/barebox,state.rst index 4c5b06d..ef66029 100644 --- a/Documentation/devicetree/bindings/barebox/barebox,state.rst +++ b/Documentation/devicetree/bindings/barebox/barebox,state.rst @@ -32,6 +32,12 @@ * ``backend``: describes where the data for this state is stored * ``backend-type``: should be ``raw`` or ``dtb``. +Optional properties: + +* ``algo``: A HMAC algorithm used to detect manipulation of the data + or header, sensible values follow this pattern ``hmac()``, + e.g. ``hmac(sha256)``. + Variable nodes -------------- @@ -105,6 +111,19 @@ the actual values of the variables. Unlike the raw state backend the dtb state backend can describe itself. +HMAC +---- + +With the optional property ``algo = "hmac()";`` a HMAC algorithm +can be defined to detect unauthorized modification of the state's +header and/or data. For this to work the HMAC and the selected hash +algorithm have to be compiled into barebox. + +The shared secret for the HMAC is requested via +``keystore_get_secret()``, using the state's name, from the barebox +simple keystore. It's up to the developer to populate the keystore via +``keystore_set_secret()`` in beforehand. + Frontend -------- diff --git a/commands/state.c b/commands/state.c index 59d1eb8..4b51759 100644 --- a/commands/state.c +++ b/commands/state.c @@ -21,26 +21,20 @@ { int opt, ret = 0; struct state *state = NULL; - int do_save = 0, do_load = 0; + int do_save = 0; const char *statename = "state"; - while ((opt = getopt(argc, argv, "sl")) > 0) { + while ((opt = getopt(argc, argv, "s")) > 0) { switch (opt) { case 's': do_save = 1; break; - case 'l': - do_load = 1; - break; default: return COMMAND_ERROR_USAGE; } } - if (do_save && do_load) - return COMMAND_ERROR_USAGE; - - if (!do_save && !do_load) { + if (!do_save) { state_info(); return 0; } @@ -56,8 +50,6 @@ if (do_save) ret = state_save(state); - else if (do_load) - ret = state_load(state); return ret; } @@ -67,13 +59,12 @@ BAREBOX_CMD_HELP_TEXT("") BAREBOX_CMD_HELP_TEXT("options:") BAREBOX_CMD_HELP_OPT ("-s", "save state") -BAREBOX_CMD_HELP_OPT ("-l", "load state") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(state) .cmd = do_state, - BAREBOX_CMD_DESC("load and save state information") - BAREBOX_CMD_OPTS("[-sl] [STATENAME]") + BAREBOX_CMD_DESC("save state information") + BAREBOX_CMD_OPTS("[-s] [STATENAME]") BAREBOX_CMD_GROUP(CMD_GRP_MISC) BAREBOX_CMD_HELP(cmd_state_help) BAREBOX_CMD_END diff --git a/common/Kconfig b/common/Kconfig index 877d385..8e79509 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -751,6 +751,24 @@ select OFTREE select PARAMETER +config STATE_CRYPTO + bool "HMAC based authentication support" + depends on STATE + select CRYPTO_KEYSTORE + select DIGEST + select DIGEST_HMAC_GENERIC + help + This options enables HMAC based authentication support for + the state's header and data. This means the state framework + can verify both the data integrity and the authentication of + the state's header and data. + + Don't forget to select a hash algorithm in the + crypto/digests menu. + + See Documentation/devicetree/bindings/barebox/barebox,state.rst + for more information. + config RESET_SOURCE bool "detect Reset cause" depends on GLOBALVAR diff --git a/common/state.c b/common/state.c index a161b94..ec72dbd 100644 --- a/common/state.c +++ b/common/state.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -28,6 +29,8 @@ #include #include +#include + #include #include #include @@ -41,7 +44,7 @@ struct state { struct device_d dev; - const struct device_node *root; + struct device_node *root; struct list_head variables; const char *name; struct list_head list; @@ -51,11 +54,11 @@ }; struct state_backend { - int (*load)(struct state_backend *backend, struct state *state); int (*save)(struct state_backend *backend, struct state *state); const char *name; const char *of_path; const char *path; + struct digest *digest; }; enum state_variable_type { @@ -951,6 +954,16 @@ if (ret) goto out; + if (state->backend->digest) { + p = of_new_property(new_node, "algo", + digest_name(state->backend->digest), + strlen(digest_name(state->backend->digest)) + 1); + if (!p) { + ret = -ENOMEM; + goto out; + } + } + /* address-cells + size-cells */ ret = of_property_write_u32(new_node, "#address-cells", 1); if (ret) @@ -1051,30 +1064,6 @@ } /* - * state_load - load a state from the backing store - * - * @state The state instance to load - */ -int state_load(struct state *state) -{ - int ret; - - if (!state->backend) - return -ENOSYS; - - ret = state->backend->load(state->backend, state); - if (ret) { - dev_warn(&state->dev, "load failed\n"); - state->dirty = 1; - } else { - dev_info(&state->dev, "load successful\n"); - state->dirty = 0; - } - - return ret; -} - -/* * state_save - save a state to the backing store * * @state The state instance to save @@ -1228,7 +1217,6 @@ backend_dtb = xzalloc(sizeof(*backend_dtb)); backend = &backend_dtb->backend; - backend->load = state_backend_dtb_load; backend->save = state_backend_dtb_save; backend->of_path = xstrdup(of_path); backend->path = xstrdup(path); @@ -1240,6 +1228,15 @@ if (!ret && !(meminfo.flags & MTD_NO_ERASE)) backend_dtb->need_erase = true; + ret = state_backend_dtb_load(backend, state); + if (ret) { + dev_warn(&state->dev, "load failed - using defaults\n"); + } else { + dev_info(&state->dev, "load successful\n"); + state->dirty = 0; + } + + /* ignore return value of load() */ return 0; } @@ -1249,7 +1246,7 @@ struct state_backend_raw { struct state_backend backend; unsigned long size_data; /* The raw data size (without header) */ - unsigned long size_full; /* The size header + raw data */ + unsigned long size_full; /* The size header + raw data + hmac */ unsigned long stride; /* The stride size in bytes of the copies */ off_t offset; /* offset in the storage file */ size_t size; /* size of the storage area */ @@ -1272,8 +1269,9 @@ struct state_variable *sv; struct backend_raw_header header = {}; unsigned long max_len; + int d_len = 0; int ret; - void *buf; + void *buf, *data, *hmac; max_len = backend_raw->stride; @@ -1304,6 +1302,11 @@ return -EINVAL; } + if (backend_raw->backend.digest) { + d_len = digest_length(backend_raw->backend.digest); + max_len -= d_len; + } + if (header.data_len > max_len) { dev_err(&state->dev, "invalid data_len %u in header, max is %lu\n", @@ -1311,13 +1314,19 @@ return -EINVAL; } - buf = xzalloc(header.data_len); + buf = xzalloc(sizeof(header) + header.data_len + d_len); + data = buf + sizeof(header); + hmac = data + header.data_len; - ret = read_full(fd, buf, header.data_len); + ret = lseek(fd, offset, SEEK_SET); if (ret < 0) goto out_free; - crc = crc32(0, buf, header.data_len); + ret = read_full(fd, buf, sizeof(header) + header.data_len + d_len); + if (ret < 0) + goto out_free; + + crc = crc32(0, data, header.data_len); if (crc != header.data_crc) { dev_err(&state->dev, "invalid crc, calculated 0x%08x, found 0x%08x\n", @@ -1326,10 +1335,27 @@ goto out_free; } + if (backend_raw->backend.digest) { + struct digest *d = backend_raw->backend.digest; + + ret = digest_init(d); + if (ret) + goto out_free; + + /* hmac over header and data */ + ret = digest_update(d, buf, sizeof(header) + header.data_len); + if (ret) + goto out_free; + + ret = digest_verify(d, hmac); + if (ret < 0) + goto out_free; + } + list_for_each_entry(sv, &state->variables, list) { if (sv->start + sv->size > header.data_len) break; - memcpy(sv->raw, buf + sv->start, sv->size); + memcpy(sv->raw, data + sv->start, sv->size); } free(buf); @@ -1406,7 +1432,7 @@ struct state_backend_raw *backend_raw = container_of(backend, struct state_backend_raw, backend); int ret = 0, fd, i; - void *buf, *data; + void *buf, *data, *hmac; struct backend_raw_header *header; struct state_variable *sv; @@ -1414,6 +1440,7 @@ header = buf; data = buf + sizeof(*header); + hmac = data + backend_raw->size_data; list_for_each_entry(sv, &state->variables, list) memcpy(data + sv->start, sv->raw, sv->size); @@ -1424,6 +1451,23 @@ header->header_crc = crc32(0, header, sizeof(*header) - sizeof(uint32_t)); + if (backend_raw->backend.digest) { + struct digest *d = backend_raw->backend.digest; + + ret = digest_init(d); + if (ret) + goto out_free; + + /* hmac over header and data */ + ret = digest_update(d, buf, sizeof(*header) + backend_raw->size_data); + if (ret) + goto out_free; + + ret = digest_final(d, hmac); + if (ret < 0) + goto out_free; + } + fd = open(backend->path, O_WRONLY); if (fd < 0) goto out_free; @@ -1508,6 +1552,53 @@ return ret; } +static int state_backend_raw_file_init_digest(struct state *state, struct state_backend_raw *backend_raw) +{ + struct digest *digest; + struct property *p; + const char *algo; + const unsigned char *key; + int key_len, ret; + + p = of_find_property(state->root, "algo", NULL); + if (!p) /* does not exist */ + return 0; + + if (!IS_ENABLED(CONFIG_STATE_CRYPTO)) { + dev_err(&state->dev, + "algo %s specified, but crypto support for state framework (CONFIG_STATE_CRYPTO) not enabled.\n", + algo); + return -EINVAL; + } + + ret = of_property_read_string(state->root, "algo", &algo); + if (ret) + return ret; + + ret = keystore_get_secret(state->name, &key, &key_len); + if (ret == -ENOENT) /* -ENOENT == does not exist */ + return -EPROBE_DEFER; + else if (ret) + return ret; + + digest = digest_alloc(algo); + if (!digest) { + dev_info(&state->dev, "algo %s not found - probe deferred\n", algo); + return -EPROBE_DEFER; + } + + ret = digest_set_key(digest, key, key_len); + if (ret) { + digest_free(digest); + return ret; + } + + backend_raw->backend.digest = digest; + backend_raw->size_full = digest_length(digest); + + return 0; +} + /* * state_backend_raw_file - create a raw file backend store for a state instance * @@ -1548,9 +1639,14 @@ return -EINVAL; backend_raw = xzalloc(sizeof(*backend_raw)); - backend = &backend_raw->backend; - backend->load = state_backend_raw_load; + ret = state_backend_raw_file_init_digest(state, backend_raw); + if (ret) { + free(backend_raw); + return ret; + } + + backend = &backend_raw->backend; backend->save = state_backend_raw_save; backend->of_path = xstrdup(of_path); backend->path = xstrdup(path); @@ -1560,7 +1656,7 @@ backend_raw->size_data = sv->start + sv->size; backend_raw->offset = offset; backend_raw->size = size; - backend_raw->size_full = backend_raw->size_data + + backend_raw->size_full += backend_raw->size_data + sizeof(struct backend_raw_header); state->backend = backend; @@ -1585,8 +1681,19 @@ goto err; } + ret = state_backend_raw_load(backend, state); + if (ret) { + dev_warn(&state->dev, "load failed - using defaults\n"); + } else { + dev_info(&state->dev, "load successful\n"); + state->dirty = 0; + } + + /* ignore return value of load() */ return 0; err: + digest_free(backend_raw->backend.digest); + free(backend_raw); return ret; } diff --git a/crypto/Kconfig b/crypto/Kconfig index ef807de..41145a3 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -1,3 +1,5 @@ +menu "Crypto support" + config CRC32 bool @@ -84,3 +86,11 @@ select DIGEST select DIGEST_SHA1_GENERIC bool + +config CRYPTO_KEYSTORE + bool "Keystore" + help + This is a simple keystore, which can be used to pass keys + between several components via simple interface. + +endmenu diff --git a/crypto/Makefile b/crypto/Makefile index f39de71..c6d1778 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_DIGEST_SHA512_GENERIC) += sha4.o obj-$(CONFIG_CRYPTO_PBKDF2) += pbkdf2.o +obj-$(CONFIG_CRYPTO_KEYSTORE) += keystore.o diff --git a/crypto/keystore.c b/crypto/keystore.c new file mode 100644 index 0000000..90b470f --- /dev/null +++ b/crypto/keystore.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 Pengutronix, Marc Kleine-Budde + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + */ + +#include +#include +#include + +static LIST_HEAD(keystore_list); + +#define for_each_key(key) list_for_each_entry(key, &keystore_list, list) + +struct keystore_key { + struct list_head list; + const char *name; + const u8 *secret; + int secret_len; +}; + +static int keystore_compare(struct list_head *a, struct list_head *b) +{ + const char *na = list_entry(a, struct keystore_key, list)->name; + const char *nb = list_entry(b, struct keystore_key, list)->name; + + return strcmp(na, nb); +} + +/** + * @param[in] name Name of the secret to get + * @param[out] secret Double pointer to memory representing the secret, do _not_ free() after use + * @param[out] secret_len Pointer to length of the secret + */ +int keystore_get_secret(const char *name, const u8 **secret, int *secret_len) +{ + struct keystore_key *key; + + for_each_key(key) { + if (!strcmp(name, key->name)) { + if (!secret || !secret_len) + return 0; + + *secret = key->secret; + *secret_len = key->secret_len; + + return 0; + } + } + + return -ENOENT; +} + +/** + * @param[in] name Name of the secret to set + * @param[in] secret Pointer to memory holding the secret + * @param[in] secret_len Length of the secret + */ +int keystore_set_secret(const char *name, const u8 *secret, int secret_len) +{ + struct keystore_key *key; + int ret; + + /* check if key is already in store */ + ret = keystore_get_secret(name, NULL, NULL); + if (!ret) + return -EBUSY; + + key = xzalloc(sizeof(*key)); + INIT_LIST_HEAD(&key->list); + key->name = xstrdup(name); + key->secret = xmemdup(secret, secret_len); + key->secret_len = secret_len; + + list_add_sort(&key->list, &keystore_list, keystore_compare); + + return 0; +} diff --git a/drivers/misc/state.c b/drivers/misc/state.c index f3e3664..73356b4 100644 --- a/drivers/misc/state.c +++ b/drivers/misc/state.c @@ -37,7 +37,7 @@ alias = of_alias_get(np); if (!alias) - alias = "state"; + alias = np->name; state = state_new_from_node(alias, np); if (IS_ERR(state)) @@ -52,34 +52,21 @@ /* guess if of_path is a path, not a phandle */ if (of_path[0] == '/' && len > 1) { ret = of_find_path(np, "backend", &path, 0); - if (ret) - goto out_release; } else { - struct device_d *dev; - struct cdev *cdev; partition_node = of_parse_phandle(np, "backend", 0); - if (!partition_node) { - ret = -ENODEV; - goto out_release; - } + if (!partition_node) + return -EINVAL; - dev = of_find_device_by_node(partition_node); - if (!list_is_singular(&dev->cdevs)) { - ret = -ENODEV; - goto out_release; - } - - cdev = list_first_entry(&dev->cdevs, struct cdev, devices_list); - if (!cdev) { - ret = -ENODEV; - goto out_release; - } - - path = asprintf("/dev/%s", cdev->name); of_path = partition_node->full_name; + ret = of_find_path_by_node(partition_node, &path, 0); } + if (ret == -ENODEV) + ret = -EPROBE_DEFER; + if (ret) + goto out_release; + ret = of_property_read_string(np, "backend-type", &backend_type); if (ret) { goto out_free; @@ -99,7 +86,6 @@ dev_info(dev, "backend: %s, path: %s, of_path: %s\n", backend_type, path, of_path); free(path); - state_load(state); return 0; out_free: diff --git a/drivers/of/of_path.c b/drivers/of/of_path.c index 992972c..6903905 100644 --- a/drivers/of/of_path.c +++ b/drivers/of/of_path.c @@ -106,6 +106,63 @@ return ret; } +static int __of_find_path(struct device_node *node, const char *propname, char **outpath, unsigned flags) +{ + struct of_path op = {}; + const char *str; + bool add_bb = false; + int i, ret; + + op.dev = of_find_device_by_node_path(node->full_name); + if (!op.dev) { + op.dev = of_find_device_by_node_path(node->parent->full_name); + if (!op.dev) + return -ENODEV; + } + + device_detect(op.dev); + + op.cdev = cdev_by_device_node(node); + + i = 1; + + while (propname) { + ret = of_property_read_string_index(node, propname, i++, &str); + if (ret) + break; + + ret = of_path_parse_one(&op, str); + if (ret) + return ret; + } + + if (!op.cdev) + return -ENOENT; + + if ((flags & OF_FIND_PATH_FLAGS_BB) && op.cdev->mtd && + mtd_can_have_bb(op.cdev->mtd)) + add_bb = true; + + *outpath = asprintf("/dev/%s%s", op.cdev->name, add_bb ? ".bb" : ""); + + return 0; +} + +/** + * of_find_path_by_node - translate a node in the devicetree to a + * barebox device path + * + * @node: the node we're interested in + * @outpath: if this function returns 0 outpath will contain the path belonging + * to the input path description. Must be freed with free(). + * @flags: use OF_FIND_PATH_FLAGS_BB to return the .bb device if available + * + */ +int of_find_path_by_node(struct device_node *node, char **outpath, unsigned flags) +{ + return __of_find_path(node, NULL, outpath, flags); +} + /** * of_find_path - translate a path description in the devicetree to a barebox * path @@ -134,11 +191,8 @@ */ int of_find_path(struct device_node *node, const char *propname, char **outpath, unsigned flags) { - struct of_path op = {}; struct device_node *rnode; - const char *path, *str; - bool add_bb = false; - int i, ret; + const char *path; path = of_get_property(node, propname, NULL); if (!path) @@ -148,37 +202,5 @@ if (!rnode) return -ENODEV; - op.dev = of_find_device_by_node_path(rnode->full_name); - if (!op.dev) { - op.dev = of_find_device_by_node_path(rnode->parent->full_name); - if (!op.dev) - return -ENODEV; - } - - device_detect(op.dev); - - op.cdev = cdev_by_device_node(rnode); - - i = 1; - - while (1) { - ret = of_property_read_string_index(node, propname, i++, &str); - if (ret) - break; - - ret = of_path_parse_one(&op, str); - if (ret) - return ret; - } - - if (!op.cdev) - return -ENOENT; - - if ((flags & OF_FIND_PATH_FLAGS_BB) && op.cdev->mtd && - mtd_can_have_bb(op.cdev->mtd)) - add_bb = true; - - *outpath = asprintf("/dev/%s%s", op.cdev->name, add_bb ? ".bb" : ""); - - return 0; + return __of_find_path(rnode, propname, outpath, flags); } diff --git a/include/crypto/keystore.h b/include/crypto/keystore.h new file mode 100644 index 0000000..2991585 --- /dev/null +++ b/include/crypto/keystore.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Pengutronix, Marc Kleine-Budde + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + */ + +#ifndef __CRYPTO_KEYSTORE_H +#define __CRYPTO_KEYSTORE_H + +#ifdef CONFIG_CRYPTO_KEYSTORE +int keystore_get_secret(const char *name, const u8 **secret, int *secret_len); +int keystore_set_secret(const char *name, const u8 *secret, int secret_len); +#else +static inline int keystore_get_secret(const char *name, const u8 **secret, int *secret_len) +{ + return -EINVAL; +} +static inline int keystore_set_secret(const char *name, const u8 *secret, int secret_len) +{ + return 0; +} +#endif + +#endif diff --git a/include/of.h b/include/of.h index e0ebc39..e60fe89 100644 --- a/include/of.h +++ b/include/of.h @@ -246,6 +246,7 @@ struct device_d *of_find_device_by_node_path(const char *path); #define OF_FIND_PATH_FLAGS_BB 1 /* return .bb device if available */ int of_find_path(struct device_node *node, const char *propname, char **outpath, unsigned flags); +int of_find_path_by_node(struct device_node *node, char **outpath, unsigned flags); int of_register_fixup(int (*fixup)(struct device_node *, void *), void *context); int of_unregister_fixup(int (*fixup)(struct device_node *, void *), void *context); struct device_node *of_find_node_by_alias(struct device_node *root, diff --git a/include/state.h b/include/state.h index 08c4e86..b3966fd 100644 --- a/include/state.h +++ b/include/state.h @@ -17,7 +17,6 @@ struct state *state_by_node(const struct device_node *node); int state_get_name(const struct state *state, char const **name); -int state_load(struct state *state); int state_save(struct state *state); void state_info(void);