* [PATCH 1/4] avb: make the AVB root key source pluggable
2026-07-27 17:14 [PATCH 0/4] avb: make the AVB root key source pluggable Igor Opaniuk
@ 2026-07-27 17:14 ` Igor Opaniuk
2026-08-10 9:41 ` Mattijs Korpershoek
2026-07-27 17:14 ` [PATCH 2/4] avb: add OP-TEE root key digest provider Igor Opaniuk
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Igor Opaniuk @ 2026-07-27 17:14 UTC (permalink / raw)
To: u-boot
Cc: Tom Rini, Mattijs Korpershoek, Peter Robinson, Quentin Schulz,
Johan Jonker, Igor Opaniuk
AVB's whole chain of trust reduces to a single public key, and that key is
currently hard-coded in U-Boot as the AVB reference/test key, whose private
half is public. That is fine for bring-up but unusable for a locked
product, which must anchor its root of trust in something an attacker
cannot swap out -- a hash fused into the SoC, a secure element -- rather
than a blob compiled into the bootloader.
Make the source of the trusted key digest selectable so a board can decide
where its root of trust lives, without touching the verification logic and
with any lookup failure treated as untrusted. The built-in key remains the
default, so existing users are unaffected; the sources a production device
actually needs are added in the following patches.
Signed-off-by: Igor Opaniuk <igor.opaniuk@gmail.com>
---
common/Kconfig | 18 ++++++++++++++++++
common/avb_verify.c | 36 +++++++++++++++++++++++++++++++++---
include/avb_verify.h | 19 +++++++++++++++++++
3 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/common/Kconfig b/common/Kconfig
index 345be4b8ca1..654e78b7b7a 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -881,6 +881,7 @@ config AVB_VERIFY
depends on MMC
depends on PARTITION_UUIDS
depends on FASTBOOT
+ select SHA256
help
This option enables compilation of bootloader-dependent operations,
used by Android Verified Boot 2.0 library (libavb). Includes:
@@ -904,6 +905,23 @@ config AVB_BUF_SIZE
AVB requires a buffer for memory transactions. This variable defines the
buffer size.
+choice
+ prompt "Source of the trusted AVB root public key"
+ default AVB_ROOT_KEY_BUILTIN
+ help
+ Selects where validate_vbmeta_public_key() obtains the trusted root
+ key digest that the vbmeta signing key is compared against. This is
+ the root of trust for the whole AVB chain.
+
+config AVB_ROOT_KEY_BUILTIN
+ bool "Built-in key hard-coded in avb_verify.c"
+ help
+ Use the avb_root_pub[] blob compiled into U-Boot. By default this is
+ the AVB reference/test key and is intended for development only;
+ replace the blob for production use.
+
+endchoice
+
endif # AVB_VERIFY
config SCP03
diff --git a/common/avb_verify.c b/common/avb_verify.c
index 76c523fd0ba..7c6baa33ed8 100644
--- a/common/avb_verify.c
+++ b/common/avb_verify.c
@@ -11,7 +11,9 @@
#include <part.h>
#include <tee.h>
#include <tee/optee_ta_avb.h>
+#include <u-boot/sha256.h>
+#if CONFIG_IS_ENABLED(AVB_ROOT_KEY_BUILTIN)
static const unsigned char avb_root_pub[1032] = {
0x0, 0x0, 0x10, 0x0, 0x55, 0xd9, 0x4, 0xad, 0xd8, 0x4,
0xaf, 0xe3, 0xd3, 0x84, 0x6c, 0x7e, 0xd, 0x89, 0x3d, 0xc2,
@@ -118,6 +120,7 @@ static const unsigned char avb_root_pub[1032] = {
0xe1, 0x74, 0xa1, 0xa3, 0x99, 0xa0, 0x85, 0x9e, 0xf1, 0xac,
0xd8, 0x7e,
};
+#endif /* AVB_ROOT_KEY_BUILTIN */
const char *str_avb_io_error(AvbIOResult res)
{
@@ -654,14 +657,26 @@ static AvbIOResult validate_vbmeta_public_key(AvbOps *ops,
public_key_metadata_length,
bool *out_key_is_trusted)
{
+ u8 key_digest[SHA256_SUM_LEN];
+ u8 trusted_digest[SHA256_SUM_LEN];
+ AvbIOResult rc;
+
if (!public_key_length || !public_key_data || !out_key_is_trusted)
return AVB_IO_RESULT_ERROR_IO;
+ /* Default deny: only flip to trusted on a positive digest match. */
*out_key_is_trusted = false;
- if (public_key_length != sizeof(avb_root_pub))
- return AVB_IO_RESULT_ERROR_IO;
- if (memcmp(avb_root_pub, public_key_data, public_key_length) == 0)
+ /* Digest of the key embedded in the (untrusted) vbmeta. */
+ sha256_csum_wd(public_key_data, public_key_length, key_digest,
+ CHUNKSZ_SHA256);
+
+ /* Digest of the trusted root key from the configured source. */
+ rc = avb_read_root_key_digest(ops, trusted_digest);
+ if (rc != AVB_IO_RESULT_OK)
+ return rc;
+
+ if (avb_safe_memcmp(key_digest, trusted_digest, sizeof(key_digest)) == 0)
*out_key_is_trusted = true;
return AVB_IO_RESULT_OK;
@@ -1040,6 +1055,21 @@ free_name:
}
#endif
+/**
+ * ============================================================================
+ * AVB root key digest providers (selected via CONFIG_AVB_ROOT_KEY_*)
+ * ============================================================================
+ */
+#if CONFIG_IS_ENABLED(AVB_ROOT_KEY_BUILTIN)
+AvbIOResult avb_read_root_key_digest(AvbOps *ops, uint8_t *digest)
+{
+ sha256_csum_wd(avb_root_pub, sizeof(avb_root_pub), digest,
+ CHUNKSZ_SHA256);
+
+ return AVB_IO_RESULT_OK;
+}
+#endif
+
/**
* ============================================================================
* AVB2.0 AvbOps alloc/initialisation/free
diff --git a/include/avb_verify.h b/include/avb_verify.h
index 5d998b5a302..66655aab871 100644
--- a/include/avb_verify.h
+++ b/include/avb_verify.h
@@ -47,6 +47,25 @@ enum mmc_io_type {
AvbOps *avb_ops_alloc(int boot_device);
void avb_ops_free(AvbOps *ops);
+/**
+ * avb_read_root_key_digest() - get the SHA-256 digest of the trusted AVB
+ * root public key
+ *
+ * This is the root of trust for AVB: validate_vbmeta_public_key() hashes the
+ * key embedded in the (untrusted) vbmeta and compares it against the digest
+ * returned here. The provider is selected via CONFIG_AVB_ROOT_KEY_* and, for
+ * CONFIG_AVB_ROOT_KEY_BOARD, may be overridden by a SoC/board specific strong
+ * definition (e.g. reading a hash fused into OTP).
+ *
+ * @ops: AvbOps, contains AVB ops handlers (needed by the TEE provider)
+ * @digest: output buffer of AVB_SHA256_DIGEST_SIZE bytes
+ *
+ * @return:
+ * AVB_IO_RESULT_OK on success (@digest populated)
+ * any other AvbIOResult fails verification closed (key not trusted)
+ */
+AvbIOResult avb_read_root_key_digest(AvbOps *ops, uint8_t *digest);
+
char *avb_set_state(AvbOps *ops, enum avb_boot_state boot_state);
char *avb_set_enforce_verity(const char *cmdline);
char *avb_set_ignore_corruption(const char *cmdline);
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 2/4] avb: add OP-TEE root key digest provider
2026-07-27 17:14 [PATCH 0/4] avb: make the AVB root key source pluggable Igor Opaniuk
2026-07-27 17:14 ` [PATCH 1/4] " Igor Opaniuk
@ 2026-07-27 17:14 ` Igor Opaniuk
2026-08-10 9:43 ` Mattijs Korpershoek
2026-07-27 17:14 ` [PATCH 3/4] avb: add board-specific root key provider Igor Opaniuk
2026-07-27 17:14 ` [PATCH 4/4] doc: android: document the AVB root key source Igor Opaniuk
3 siblings, 1 reply; 9+ messages in thread
From: Igor Opaniuk @ 2026-07-27 17:14 UTC (permalink / raw)
To: u-boot
Cc: Tom Rini, Mattijs Korpershoek, Peter Robinson, Quentin Schulz,
Johan Jonker, Igor Opaniuk
When a device already runs OP-TEE, its root-of-trust key should live in the
same secure, RPMB-backed storage that already holds the AVB rollback
indexes and lock state, so it is provisioned per device and protected by
the secure world -- not shipped inside the bootloader image.
Let AVB fetch the trusted digest from the OP-TEE AVB TA. It reuses the
existing persistent-value interface, so no change to the TA is required,
and a missing value fails closed.
Signed-off-by: Igor Opaniuk <igor.opaniuk@gmail.com>
---
common/Kconfig | 23 +++++++++++++++++++++++
common/avb_verify.c | 23 +++++++++++++++++++++++
2 files changed, 46 insertions(+)
diff --git a/common/Kconfig b/common/Kconfig
index 654e78b7b7a..094635bfe57 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -920,8 +920,31 @@ config AVB_ROOT_KEY_BUILTIN
the AVB reference/test key and is intended for development only;
replace the blob for production use.
+config AVB_ROOT_KEY_TEE
+ bool "Read root key digest from OP-TEE"
+ depends on OPTEE_TA_AVB
+ help
+ Obtain the SHA-256 digest of the trusted root key from OP-TEE secure
+ storage, read as a named persistent value ("avb.root_pub_digest").
+ The digest must be provisioned into the TEE beforehand. Integrity is
+ provided by the TEE's (RPMB-backed) storage.
+
endchoice
+config AVB_ROOT_KEY_TEE_NAME
+ string "TEE persistent-value name holding the root key digest"
+ depends on AVB_ROOT_KEY_TEE
+ default "avb.root_pub_digest"
+ help
+ Named persistent value read from the OP-TEE AVB TA (via
+ TA_AVB_CMD_READ_PERSIST_VALUE) that stores the 32-byte SHA-256 digest
+ of the trusted root key. It must match the name used to provision the
+ digest into the TEE. The TA prefixes stored names with "named_value_"
+ internally, so this string is opaque to the TA; the "avb." prefix only
+ follows libavb's named-persistent-value convention and does not collide
+ with libavb's own reserved names (avb.persistent_digest.*,
+ avb.managed_verity_mode).
+
endif # AVB_VERIFY
config SCP03
diff --git a/common/avb_verify.c b/common/avb_verify.c
index 7c6baa33ed8..b8b99628180 100644
--- a/common/avb_verify.c
+++ b/common/avb_verify.c
@@ -1068,6 +1068,29 @@ AvbIOResult avb_read_root_key_digest(AvbOps *ops, uint8_t *digest)
return AVB_IO_RESULT_OK;
}
+#elif CONFIG_IS_ENABLED(AVB_ROOT_KEY_TEE)
+/*
+ * Read the trusted root key digest from OP-TEE secure storage as a named
+ * persistent value, using the existing TA_AVB_CMD_READ_PERSIST_VALUE command
+ * (see OP-TEE ta/avb/entry.c). The digest must be provisioned into the TEE
+ * beforehand under CONFIG_AVB_ROOT_KEY_TEE_NAME; a missing value returns
+ * AVB_IO_RESULT_ERROR_NO_SUCH_VALUE and verification fails closed.
+ */
+AvbIOResult avb_read_root_key_digest(AvbOps *ops, uint8_t *digest)
+{
+ size_t num_read = 0;
+ AvbIOResult rc;
+
+ rc = read_persistent_value(ops, CONFIG_AVB_ROOT_KEY_TEE_NAME,
+ SHA256_SUM_LEN, digest, &num_read);
+ if (rc != AVB_IO_RESULT_OK)
+ return rc;
+
+ if (num_read != SHA256_SUM_LEN)
+ return AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE;
+
+ return AVB_IO_RESULT_OK;
+}
#endif
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 3/4] avb: add board-specific root key provider
2026-07-27 17:14 [PATCH 0/4] avb: make the AVB root key source pluggable Igor Opaniuk
2026-07-27 17:14 ` [PATCH 1/4] " Igor Opaniuk
2026-07-27 17:14 ` [PATCH 2/4] avb: add OP-TEE root key digest provider Igor Opaniuk
@ 2026-07-27 17:14 ` Igor Opaniuk
2026-08-10 9:44 ` Mattijs Korpershoek
2026-07-27 17:14 ` [PATCH 4/4] doc: android: document the AVB root key source Igor Opaniuk
3 siblings, 1 reply; 9+ messages in thread
From: Igor Opaniuk @ 2026-07-27 17:14 UTC (permalink / raw)
To: u-boot
Cc: Tom Rini, Mattijs Korpershoek, Peter Robinson, Quentin Schulz,
Johan Jonker, Igor Opaniuk
The strongest anchor for the root of trust is a key hash burned into
hardware (OTP/eFuse), which is inherently SoC-specific and cannot live in
common code. Give boards a hook to supply it from wherever their hardware
keeps it.
The default is deliberately fail-closed: a board that has not wired up a
provider refuses verification rather than silently trusting whatever key it
is handed. A misconfiguration should stop the boot, not quietly weaken it.
Signed-off-by: Igor Opaniuk <igor.opaniuk@gmail.com>
---
common/Kconfig | 9 +++++++++
common/avb_verify.c | 13 +++++++++++++
2 files changed, 22 insertions(+)
diff --git a/common/Kconfig b/common/Kconfig
index 094635bfe57..110eaa0d004 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -929,6 +929,15 @@ config AVB_ROOT_KEY_TEE
The digest must be provisioned into the TEE beforehand. Integrity is
provided by the TEE's (RPMB-backed) storage.
+config AVB_ROOT_KEY_BOARD
+ bool "SoC/board specific provider (weak function)"
+ help
+ Obtain the trusted root key digest from a board/SoC specific strong
+ definition of avb_read_root_key_digest() (for example reading a hash
+ fused into OTP/eFuse). The default weak implementation fails closed,
+ so a board that forgets to override it will refuse verification
+ rather than silently trust a wrong key.
+
endchoice
config AVB_ROOT_KEY_TEE_NAME
diff --git a/common/avb_verify.c b/common/avb_verify.c
index b8b99628180..b96cd1ebd7b 100644
--- a/common/avb_verify.c
+++ b/common/avb_verify.c
@@ -1091,6 +1091,19 @@ AvbIOResult avb_read_root_key_digest(AvbOps *ops, uint8_t *digest)
return AVB_IO_RESULT_OK;
}
+#else /* CONFIG_AVB_ROOT_KEY_BOARD */
+/*
+ * Weak, fail-closed default. A board/SoC selecting CONFIG_AVB_ROOT_KEY_BOARD
+ * must provide a strong avb_read_root_key_digest() (e.g. reading a hash fused
+ * into OTP/eFuse). If it does not, verification fails rather than silently
+ * trusting a wrong key.
+ */
+__weak AvbIOResult avb_read_root_key_digest(AvbOps *ops, uint8_t *digest)
+{
+ printf("%s: board root key provider not implemented\n", __func__);
+
+ return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
+}
#endif
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 4/4] doc: android: document the AVB root key source
2026-07-27 17:14 [PATCH 0/4] avb: make the AVB root key source pluggable Igor Opaniuk
` (2 preceding siblings ...)
2026-07-27 17:14 ` [PATCH 3/4] avb: add board-specific root key provider Igor Opaniuk
@ 2026-07-27 17:14 ` Igor Opaniuk
2026-08-10 9:46 ` Mattijs Korpershoek
3 siblings, 1 reply; 9+ messages in thread
From: Igor Opaniuk @ 2026-07-27 17:14 UTC (permalink / raw)
To: u-boot
Cc: Tom Rini, Mattijs Korpershoek, Peter Robinson, Quentin Schulz,
Johan Jonker, Igor Opaniuk
The root-key choice carries security pitfalls that are not obvious from the
Kconfig prompts alone: the built-in key is only a test key, a digest stored
as a TEE persistent value is worthless unless the TA refuses to overwrite
it while the device is unlocked, and the board provider must fail closed.
Spell these out so an integrator does not ship an insecure default by
accident.
Signed-off-by: Igor Opaniuk <igor.opaniuk@gmail.com>
---
doc/android/avb2.rst | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/doc/android/avb2.rst b/doc/android/avb2.rst
index 178e8a2681e..b79b2468968 100644
--- a/doc/android/avb2.rst
+++ b/doc/android/avb2.rst
@@ -37,6 +37,43 @@ indexes and device lock state are stored in RPMB. The RPMB partition is managed
by OP-TEE (see [2]_ for details) which is a secure OS leveraging ARM
TrustZone.
+Root of trust
+-------------
+
+AVB anchors its chain of trust in a single public key: the key embedded in
+the vbmeta image is attacker-controlled, so ``validate_vbmeta_public_key()``
+hashes it (SHA-256) and compares the digest against a *trusted* digest. The
+source of that trusted digest is selected by the ``CONFIG_AVB_ROOT_KEY_*``
+choice. Any error obtaining the trusted digest fails closed, i.e. the vbmeta
+key is treated as untrusted.
+
+``AVB_ROOT_KEY_BUILTIN`` (default)
+ Use the SHA-256 of the ``avb_root_pub`` blob compiled into U-Boot. By
+ default this is the AVB reference/test key, whose private half is publicly
+ available; it is intended for development only and MUST be replaced for
+ production. The built-in key is only a meaningful root of trust if the
+ U-Boot image itself is verified by an earlier boot stage.
+
+``AVB_ROOT_KEY_TEE``
+ Read the trusted digest from OP-TEE secure storage as a named persistent
+ value (``CONFIG_AVB_ROOT_KEY_TEE_NAME``, default ``avb.root_pub_digest``)
+ via the OP-TEE AVB TA. Requires ``CONFIG_OPTEE_TA_AVB``. The 32-byte digest
+ must be provisioned into the TEE beforehand. Note that the OP-TEE AVB TA
+ lets normal world write arbitrary persistent values, so for this to be a
+ real anchor the TA must reject writes to this value while the device is
+ locked; otherwise it can be overwritten from normal world.
+
+``AVB_ROOT_KEY_BOARD``
+ Obtain the trusted digest from a board/SoC specific strong definition of
+ ``avb_read_root_key_digest()`` (for example reading a hash fused into
+ OTP/eFuse). The default weak implementation fails closed, so a board that
+ forgets to override it refuses verification rather than silently trusting a
+ wrong key.
+
+Note that device lock state and rollback protection are only enforced when
+backed by OP-TEE (see `AVB using OP-TEE (optional)`_); without it,
+verification is advisory regardless of the root key source.
+
AVB 2.0 U-Boot shell commands
-----------------------------
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread