Skip to contentSkip to conversation

public keys: rework keyrings as nested containers

PATCH Applied Sascha Hauer 1 participant 6 messages May 27 – May 29

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

cover public keys: rework keyrings as nested containers

So far a public key had a char *keyring meaning that a key can only be part of a single keyring. This series overcomes this, with it keys can be contained in multiple keyrings and keyrings can be part of other keyrings. To get there a new struct keyring_link is introduced which is instanciated per link and links a key/keyring to a keyring. The keys command is updated to reflect that, so an example output could be:

RING: fit

    KEY:    9160ff3ab26e1842d1fbfaca48befce2664f57245048856e779531bb260cbc20	TYPE: ECDSA	HINT:
    RING:   snakeoil
    RING:   fit-devel
RING: tlv-generic
    RING:   snakeoil
RING: snakeoil
    KEY:    4f48c67c9a28be596931de5b137f8207f15a505d1b7a742731014dbcd4c799d9	TYPE: ECDSA	HINT: ecdsa-devel
    KEY:    7fbcb3b2ddfab044ef874550c7cbd1b357afeed55fb6f48be9c22b2cbef72856	TYPE: RSA	HINT: rsa-devel
RING: fit-devel
    KEY:    7fbcb3b2ddfab044ef874550c7cbd1b357afeed55fb6f48be9c22b2cbef72856	TYPE: RSA	HINT:
RING: tlv-feature
    KEY:    54b7b9c040cd81dd1e495832b8a2724a8f48e75f90fcd982631695669a805dd0	TYPE: RSA	HINT:
RING: tlv-base
    KEY:    2f908960b1d74b3a8d0076376870020f8d6504ebb7a1c68c1c383ac8140690e5	TYPE: RSA	HINT:

The FIT image authentication code still uses the "fit" keyring as before, but now we can link new keys to the keyring during runtime as needed. Furthermore a key can be used for multiple purposes without duplicating it.

As a nice side effect the keys themselves become readonly and the compiled in keys can be moved to the RO data segment.

---
Sascha Hauer (4):
public keys: make error message more informative public keys: make key_name_hint optional
public keys: rework keyrings as nested containers public keys: allow keys to be members of multiple keyrings

 commands/keys.c                   |  21 +++-
 common/image-fit.c                |  15 ++-
 common/tlv/parser.c               |  13 ++-
 crypto/Makefile                   |   6 +-
 crypto/public-keys.c              | 221 ++++++++++++++++++++++++++++++++++----
 crypto/rsa.c                      |   3 +-
 include/asm-generic/barebox.lds.h |  11 ++
 include/crypto/public_key.h       |  87 ++++++++++++---
 scripts/keytoc.c                  |  79 +++++++++-----
 9 files changed, 381 insertions(+), 75 deletions(-)

---
base-commit: 19ce0bdebf4856cfe6d0610a6e4d8d917d658ec3
change-id: 20260522-public-keys-ba609c65c114

Best regards,

Signature · 1 line
Sascha Hauer <[email protected]>
#1 lore raw

PATCH 1/4 public keys: make error message more informative

When adding a key to a keyring fails because a key with the same name hint already exists, print which keyring and which hint is duplicate. This also removes the string "fit" from the error message which seems to be a remnant of days when only FIT images used keys.

---
 crypto/public-keys.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
crypto/public-keys.c +2 -1
index a2d31f4bc2..ecf255bbb4 100644
@@ -29,7 +29,8 @@ int public_key_add(struct public_key *key)
 	}
 
 	if (public_key_get(key->key_name_hint, key->keyring)) {
-		pr_warn("Aborting addition of public key: Duplicate fit name hint\n");
+		pr_warn("Cannot add key: key_name_hint %s already exists in keyring %s\n",
+			key->key_name_hint, key->keyring);
 		return -EEXIST;
 	}
 
Signature · 1 line
2.47.3
#2 lore raw

PATCH 2/4 public keys: make key_name_hint optional

In reply to Sascha Hauer

The key_name_hint is merely a hint which key might fit and as such it should be optional for a key. In keytoc.c only set it when it has meaningful content and test for non NULL in barebox before dereferencing it.

---
 commands/keys.c      |  3 ++-
 crypto/public-keys.c |  2 ++
 scripts/keytoc.c     | 10 ++++------
 3 files changed, 8 insertions(+), 7 deletions(-)
commands/keys.c +2 -1
index 83c3ed533d..12cb6ea2e3 100644
@@ -9,7 +9,8 @@ static int do_keys(int argc, char *argv[])
 
 	for_each_public_key(key, id) {
 		printf("KEY: %*phN\tTYPE: %s\tKEYRING: %s\tHINT: %s\n", key->hashlen,
-		       key->hash, public_key_type_string(key->type), key->keyring, key->key_name_hint);
+		       key->hash, public_key_type_string(key->type), key->keyring,
+		       key->key_name_hint ?: "");
 	}
 
 	return 0;
crypto/public-keys.c +2 -0
index ecf255bbb4..2b4bac55b7 100644
@@ -14,6 +14,8 @@ const struct public_key *public_key_get(const char *name, const char *keyring)
 	int id;
 
 	for_each_public_key_keyring(key, id, keyring) {
+		if (!key->key_name_hint)
+			continue;
 		if (!strcmp(key->key_name_hint, name))
 			return key;
 	}
scripts/keytoc.c +4 -6
index c4491fbe81..40601827b7 100644
@@ -566,7 +566,8 @@ static int gen_key_ecdsa(EVP_PKEY *key, struct keyinfo *info)
 		if (!standalone) {
 			fprintf(outfilep, "\nstatic struct public_key %s_public_key = {\n", info->name_c);
 			fprintf(outfilep, "\t.type = PUBLIC_KEY_TYPE_ECDSA,\n");
-			fprintf(outfilep, "\t.key_name_hint = \"%s\",\n", info->name_hint);
+			if (info->name_hint)
+				fprintf(outfilep, "\t.key_name_hint = \"%s\",\n", info->name_hint);
 			fprintf(outfilep, "\t.keyring = \"%s\",\n", info->keyring);
 			fprintf(outfilep, "\t.hash = %s_hash,\n", info->name_c);
 			fprintf(outfilep, "\t.hashlen = %u,\n", SHA256_DIGEST_LENGTH);
@@ -673,7 +674,8 @@ static int gen_key_rsa(EVP_PKEY *key, struct keyinfo *info)
 		if (!standalone) {
 			fprintf(outfilep, "\nstatic struct public_key %s_public_key = {\n", info->name_c);
 			fprintf(outfilep, "\t.type = PUBLIC_KEY_TYPE_RSA,\n");
-			fprintf(outfilep, "\t.key_name_hint = \"%s\",\n", info->name_hint);
+			if (info->name_hint)
+				fprintf(outfilep, "\t.key_name_hint = \"%s\",\n", info->name_hint);
 			fprintf(outfilep, "\t.keyring = \"%s\",\n", info->keyring);
 			fprintf(outfilep, "\t.hash = %s_hash,\n", info->name_c);
 			fprintf(outfilep, "\t.hashlen = %u,\n", SHA256_DIGEST_LENGTH);
@@ -920,10 +922,6 @@ int main(int argc, char *argv[])
 		if (asprintf(&info->name_c, "key_%i", keys_idx + 1) < 0)
 			enomem_exit("asprintf");
 
-		/* unfortunately, the fit name hint is mandatory in the barebox codebase */
-		if (!info->name_hint)
-			info->name_hint = info->name_c;
-
 		if (!info->keyring) {
 			info->keyring = strdup("fit");
 			fprintf(stderr, "Warning: No keyring provided in keyspec, defaulting to keyring=fit for %s\n", info->path);
Signature · 1 line
2.47.3
#3 lore raw

PATCH 3/4 public keys: rework keyrings as nested containers

In reply to Sascha Hauer

Until now a keyring was just a string-valued field on each struct public_key, and a key could only belong to a single keyring. Switching policy at runtime required changing that string in place.

Introduce explicit struct keyring / struct keyring_link types: a keyring is a named container of links, and each link references either a public_key or a sub-keyring. Sub-keyring links are live references, so adding a key to fit-devel after linking fit-devel into fit makes the key visible via fit too.

Drop the global public_keys IDR; keyrings are tracked in a flat name registry instead, and iteration goes through the keyring tree.

Change the "keys" command to print the current keyring hierarchy.

As a nice side effect separating the public keys entirely from the keyring they are in allows us to put the compiled in keys into RO data.

Assisted-by Claude Opus 4.7
---
 commands/keys.c                   |  22 ++--
 common/image-fit.c                |  15 ++-
 common/tlv/parser.c               |  13 ++-
 crypto/public-keys.c              | 218 ++++++++++++++++++++++++++++++++++----
 crypto/rsa.c                      |   3 +-
 include/asm-generic/barebox.lds.h |  11 ++
 include/crypto/public_key.h       |  87 ++++++++++++---
 scripts/keytoc.c                  |  24 +++--
 8 files changed, 336 insertions(+), 57 deletions(-)
commands/keys.c +16 -6
index 12cb6ea2e3..06d289f18f 100644
@@ -4,13 +4,23 @@
 
 static int do_keys(int argc, char *argv[])
 {
-	const struct public_key *key;
-	int id;
+	const struct keyring *kr;
+	const struct keyring_link *link;
 
-	for_each_public_key(key, id) {
-		printf("KEY: %*phN\tTYPE: %s\tKEYRING: %s\tHINT: %s\n", key->hashlen,
-		       key->hash, public_key_type_string(key->type), key->keyring,
-		       key->key_name_hint ?: "");
+	for_each_keyring(kr) {
+		printf("RING: %s\n", kr->name);
+		for_each_link_in_keyring(link, kr) {
+			if (link->type == KEYRING_LINK_KEY) {
+				const struct public_key *key = link->key;
+
+				printf("    KEY:    %*phN\tTYPE: %s\tHINT: %s\n",
+				       key->hashlen, key->hash,
+				       public_key_type_string(key->type),
+				       key->key_name_hint ?: "");
+			} else {
+				printf("    RING:   %s\n", link->keyring->name);
+			}
+		}
 	}
 
 	return 0;
common/image-fit.c +11 -4
index f5356be33d..b7daf433c3 100644
@@ -253,10 +253,11 @@ static int fit_check_signature(struct fit_handle *handle, struct device_node *si
 {
 	const char *fail_reason;
 	const struct public_key *key;
+	const struct keyring *fit_kr;
 	const char *key_name = NULL;
 	int sig_len;
 	const char *sig_value;
-	int id, ret;
+	int ret;
 
 	sig_value = of_get_property(sig_node, "value", &sig_len);
 	if (!sig_value) {
@@ -266,10 +267,16 @@ static int fit_check_signature(struct fit_handle *handle, struct device_node *si
 
 	fail_reason = "no matching keys";
 
+	fit_kr = keyring_find("fit");
+	if (!fit_kr) {
+		pr_err("fit keyring not registered\n");
+		return -ENOKEY;
+	}
+
 	of_property_read_string(sig_node, "key-name-hint", &key_name);
 	if (key_name) {
-		key = public_key_get(key_name, "fit");
-		if (key) {
+		key = keyring_find_key(fit_kr, key_name);
+		if (!IS_ERR(key)) {
 			fail_reason = "verification failed";
 			ret = public_key_verify(key, sig_value, sig_len, hash, algo);
 			if (handle->verbose)
@@ -280,7 +287,7 @@ static int fit_check_signature(struct fit_handle *handle, struct device_node *si
 		}
 	}
 
-	for_each_public_key_keyring(key, id, "fit") {
+	for_each_key_in_keyring(key, fit_kr) {
 
 		/* Don't recheck with same key as before */
 		if (key_name && streq_ptr(key->key_name_hint, key_name))
common/tlv/parser.c +10 -3
index 4c0b6b5c6f..d61dfa2c47 100644
@@ -49,15 +49,16 @@ static int tlv_verify_try_key(const struct public_key *key, const uint8_t *sig,
 	return ret;
 }
 
-static int tlv_verify(const struct tlv_header *header, const char *keyring)
+static int tlv_verify(const struct tlv_header *header, const char *keyring_name)
 {
 	const struct public_key *key;
+	const struct keyring *kr;
 	size_t payload_sz = tlv_spki_hash_offset(header);
 	const void *spki_tlv_ptr = (void *)header + payload_sz;
 	u32 spki_tlv = get_unaligned_le32(spki_tlv_ptr);
 	int SPKI_LEN = 4;
 	u32 sig_len = get_unaligned_be16(&header->length_sig);
-	int ret, id;
+	int ret;
 	int count_spki_matches = 0;
 
 	if (!IS_ENABLED(CONFIG_TLV_SIGNATURE)) {
@@ -68,7 +69,13 @@ static int tlv_verify(const struct tlv_header *header, const char *keyring)
 		return -EPROTO;
 	}
 
-	for_each_public_key_keyring(key, id, keyring) {
+	kr = keyring_find(keyring_name);
+	if (!kr) {
+		pr_warn("TLV keyring %s not found\n", keyring_name);
+		return -ENOKEY;
+	}
+
+	for_each_key_in_keyring(key, kr) {
 		u32 spki_key = get_unaligned_le32(key->hash);
 
 		if (spki_key == spki_tlv) {
crypto/public-keys.c +199 -19

255 lines not on this page · raw message

crypto/rsa.c +1 -2
index 0e752f11b4..0b20b59477 100644
@@ -468,8 +468,7 @@ static void rsa_init_keys_of(void)
 			continue;
 		}
 
-		key->keyring = "fit";
-		ret = public_key_add(key);
+		ret = public_key_add("fit", key);
 		if (ret)
 			pr_err("Cannot add rsa key %s: %pe\n",
 				key->key_name_hint, ERR_PTR(ret));
include/asm-generic/barebox.lds.h +11 -0
index 12082d567a..008217e808 100644
@@ -129,7 +129,18 @@
 #endif
 
 
+/*
+ * GCC emits "const data with absolute relocations" into .data.rel.ro*
+ * (e.g. a `static const struct` that contains pointers to other symbols).
+ * In a statically linked image these are fully resolved at link time and
+ * never written at runtime, so place them with .rodata instead of letting
+ * them fall through into the writable .data section.
+ */
+#define BAREBOX_RELRO_DATA			\
+	*(.data.rel.ro .data.rel.ro.*)
+
 #define RO_DATA_SECTION				\
+	BAREBOX_RELRO_DATA			\
 	BAREBOX_INITCALLS			\
 	BAREBOX_EXITCALLS			\
 	BAREBOX_CMDS				\
include/crypto/public_key.h +74 -13

109 lines not on this page · raw message

scripts/keytoc.c +14 -10
index 40601827b7..7e910422a7 100644
@@ -535,7 +535,7 @@ static int gen_key_ecdsa(EVP_PKEY *key, struct keyinfo *info)
 		fprintf(stderr, "ERROR: generating a dts snippet for ECDSA keys is not yet supported\n");
 		return -EOPNOTSUPP;
 	} else {
-		fprintf(outfilep, "\nstatic unsigned char %s_hash[] = {\n\t", info->name_c);
+		fprintf(outfilep, "\nstatic const unsigned char %s_hash[] = {\n\t", info->name_c);
 
 		ret = print_hash(key);
 		if (ret)
@@ -557,24 +557,26 @@ static int gen_key_ecdsa(EVP_PKEY *key, struct keyinfo *info)
 
 		fprintf(outfilep, "\n};\n\n");
 
-		fprintf(outfilep, "static struct ecdsa_public_key %s = {\n", info->name_c);
+		fprintf(outfilep, "static const struct ecdsa_public_key %s = {\n", info->name_c);
 
 		fprintf(outfilep, "\t.curve_name = \"%s\",\n", group);
 		fprintf(outfilep, "\t.x = %s_x,\n", info->name_c);
 		fprintf(outfilep, "\t.y = %s_y,\n", info->name_c);
 		fprintf(outfilep, "};\n");
 		if (!standalone) {
-			fprintf(outfilep, "\nstatic struct public_key %s_public_key = {\n", info->name_c);
+			fprintf(outfilep, "\nstatic const struct public_key %s_public_key = {\n", info->name_c);
 			fprintf(outfilep, "\t.type = PUBLIC_KEY_TYPE_ECDSA,\n");
 			if (info->name_hint)
 				fprintf(outfilep, "\t.key_name_hint = \"%s\",\n", info->name_hint);
-			fprintf(outfilep, "\t.keyring = \"%s\",\n", info->keyring);
 			fprintf(outfilep, "\t.hash = %s_hash,\n", info->name_c);
 			fprintf(outfilep, "\t.hashlen = %u,\n", SHA256_DIGEST_LENGTH);
 			fprintf(outfilep, "\t.ecdsa = &%s,\n", info->name_c);
 			fprintf(outfilep, "};\n");
 			fprintf(outfilep, "\n");
-			fprintf(outfilep, "const struct public_key *__%s_public_key __ll_elem(.public_keys.rodata.%s) = &%s_public_key;\n", info->name_c, info->name_c, info->name_c);
+			fprintf(outfilep, "static const struct public_key_record __%s_public_key __ll_elem(.public_keys.rodata.%s) = {\n", info->name_c, info->name_c);
+			fprintf(outfilep, "\t.keyring = \"%s\",\n", info->keyring);
+			fprintf(outfilep, "\t.key = &%s_public_key,\n", info->name_c);
+			fprintf(outfilep, "};\n");
 		}
 	}
 
@@ -635,7 +637,7 @@ static int gen_key_rsa(EVP_PKEY *key, struct keyinfo *info)
 		fprintf(outfilep, "\t\t\tkey-name-hint = \"%s\";\n", info->name_c);
 		fprintf(outfilep, "\t\t};\n");
 	} else {
-		fprintf(outfilep, "\nstatic unsigned char %s_hash[] = {\n\t", info->name_c);
+		fprintf(outfilep, "\nstatic const unsigned char %s_hash[] = {\n\t", info->name_c);
 
 		ret = print_hash(key);
 		if (ret)
@@ -661,7 +663,7 @@ static int gen_key_rsa(EVP_PKEY *key, struct keyinfo *info)
 			fprintf(outfilep, "struct rsa_public_key __key_%s;\n", info->name_c);
 			fprintf(outfilep, "struct rsa_public_key __key_%s = {\n", info->name_c);
 		} else {
-			fprintf(outfilep, "static struct rsa_public_key %s = {\n", info->name_c);
+			fprintf(outfilep, "static const struct rsa_public_key %s = {\n", info->name_c);
 		}
 
 		fprintf(outfilep, "\t.len = %d,\n", bits / 32);
@@ -672,17 +674,19 @@ static int gen_key_rsa(EVP_PKEY *key, struct keyinfo *info)
 		fprintf(outfilep, "};\n");
 
 		if (!standalone) {
-			fprintf(outfilep, "\nstatic struct public_key %s_public_key = {\n", info->name_c);
+			fprintf(outfilep, "\nstatic const struct public_key %s_public_key = {\n", info->name_c);
 			fprintf(outfilep, "\t.type = PUBLIC_KEY_TYPE_RSA,\n");
 			if (info->name_hint)
 				fprintf(outfilep, "\t.key_name_hint = \"%s\",\n", info->name_hint);
-			fprintf(outfilep, "\t.keyring = \"%s\",\n", info->keyring);
 			fprintf(outfilep, "\t.hash = %s_hash,\n", info->name_c);
 			fprintf(outfilep, "\t.hashlen = %u,\n", SHA256_DIGEST_LENGTH);
 			fprintf(outfilep, "\t.rsa = &%s,\n", info->name_c);
 			fprintf(outfilep, "};\n");
 			fprintf(outfilep, "\n");
-			fprintf(outfilep, "const struct public_key *__%s_public_key __ll_elem(.public_keys.rodata.%s) = &%s_public_key;\n", info->name_c, info->name_c, info->name_c);
+			fprintf(outfilep, "static const struct public_key_record __%s_public_key __ll_elem(.public_keys.rodata.%s) = {\n", info->name_c, info->name_c);
+			fprintf(outfilep, "\t.keyring = \"%s\",\n", info->keyring);
+			fprintf(outfilep, "\t.key = &%s_public_key,\n", info->name_c);
+			fprintf(outfilep, "};\n");
 		}
 	}
 
Signature · 1 line
2.47.3
#4 lore raw

PATCH 4/4 public keys: allow keys to be members of multiple keyrings

In reply to Sascha Hauer

The same signing identity can be valid in more than one context — e.g. the development PEM is used both as a FIT image signer and as a TLV signer. Until now keytoc forced one keyring per key, so the Kconfig default listed the snakeoil PEM twice (once per target keyring) and emitted two full copies of the modulus / rr / hash / public_key struct into rodata.

Let the keyring= option in a keyspec appear more than once. Each occurrence appends to a list; the emitted key data (struct public_key, rsa/ecdsa_public_key, hash) is unchanged and singular, and one struct public_key_record is emitted per keyring all pointing back at the same key. Symbol/section names are uniquified with a per-key record index (key_N_record_M).

For the development-key default, collapse the two CRYPTO_PUBLIC_KEYS entries into one (keyring=fit,keyring=tlv-generic,fit-hint=...). With 4096-bit RSA this avoids re-emitting ~1 KB per shared identity into rodata.

While here, have the keys command print a keyring header so the now N-records-per-key output is readable.

Assisted-by Claude Opus 4.7
---
 crypto/Makefile  |  6 ++----
 scripts/keytoc.c | 61 ++++++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 46 insertions(+), 21 deletions(-)
crypto/Makefile +2 -4
index 17043316c4..0c4a900504 100644
@@ -33,12 +33,10 @@ CONFIG_CRYPTO_PUBLIC_KEYS := $(foreach d,$(CONFIG_CRYPTO_PUBLIC_KEYS),"$(d)")
 
 ifdef CONFIG_CRYPTO_BUILTIN_DEVELOPMENT_KEYS
 ifdef CONFIG_CRYPTO_RSA
-CONFIG_CRYPTO_PUBLIC_KEYS += keyring=fit,fit-hint=rsa-devel:$(srctree)/crypto/snakeoil-4096-development.pem
-CONFIG_CRYPTO_PUBLIC_KEYS += keyring=tlv-generic:$(srctree)/crypto/snakeoil-4096-development.pem
+CONFIG_CRYPTO_PUBLIC_KEYS += keyring=fit,keyring=tlv-generic,fit-hint=rsa-devel:$(srctree)/crypto/snakeoil-4096-development.pem
 endif
 ifdef CONFIG_CRYPTO_ECDSA
-CONFIG_CRYPTO_PUBLIC_KEYS += keyring=fit,fit-hint=ecdsa-devel:$(srctree)/crypto/snakeoil-ecdsa-development.pem
-CONFIG_CRYPTO_PUBLIC_KEYS += keyring=tlv-generic:$(srctree)/crypto/snakeoil-ecdsa-development.pem
+CONFIG_CRYPTO_PUBLIC_KEYS += keyring=fit,keyring=tlv-generic,fit-hint=ecdsa-devel:$(srctree)/crypto/snakeoil-ecdsa-development.pem
 endif
 endif
 
scripts/keytoc.c +44 -17

109 lines not on this page · raw message

Signature · 1 line
2.47.3
author Applied
#5 lore raw
In reply to Sascha Hauer
Quoted 9 lines · Sascha Hauer

So far a public key had a char *keyring meaning that a key can only be part of a single keyring. This series overcomes this, with it keys can be contained in multiple keyrings and keyrings can be part of other keyrings. To get there a new struct keyring_link is introduced which is instanciated per link and links a key/keyring to a keyring. The keys command is updated to reflect that, so an example output could be:

[...]

Applied, thanks!

[1/4] public keys: make error message more informative
https://git.pengutronix.de/cgit/barebox/commit/?id=7c1e2e1a75e2 (link may not be stable)
[2/4] public keys: make key_name_hint optional
https://git.pengutronix.de/cgit/barebox/commit/?id=7d7cd5b01e33 (link may not be stable)
[3/4] public keys: rework keyrings as nested containers
https://git.pengutronix.de/cgit/barebox/commit/?id=499d868a876e (link may not be stable)
[4/4] public keys: allow keys to be members of multiple keyrings
https://git.pengutronix.de/cgit/barebox/commit/?id=fac191dd0c97 (link may not be stable)

Best regards,

Signature · 1 line
Sascha Hauer <[email protected]>