diff --git a/config/examples/polarfire_mpfs250_m.config b/config/examples/polarfire_mpfs250_m.config index c566100cd0..df9b5617a2 100644 --- a/config/examples/polarfire_mpfs250_m.config +++ b/config/examples/polarfire_mpfs250_m.config @@ -139,6 +139,15 @@ CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT #ENCRYPT_WITH_AES256=1 #OBJS_EXTRA=src/my_custom_encrypt_key.o +# Keep the golden image encrypted across a failover zeroize: its key lives in +# a second sNVM module that the zeroize never erases. Needs the encrypted build +# above plus SNVM_KEK=1 and DISK_GOLDEN_SLOT=1. SNVM_GOLDEN_ENCKEY_MODULE has no +# default: it must name a writable plaintext sNVM client in your Libero design +# (check one with MPFS_SNVM_DUMP first). Sign the golden image with that module's +# key and the A/B images with module 201. +#DISK_GOLDEN_ENCRYPT_KEY=1 +#SNVM_GOLDEN_ENCKEY_MODULE=199 + # Used by test-application/ELF wrapper WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000 WOLFBOOT_PARTITION_SIZE=0x4000000 diff --git a/docs/PolarFire.md b/docs/PolarFire.md index a8ca7c3c6f..b4abe3ed3d 100644 --- a/docs/PolarFire.md +++ b/docs/PolarFire.md @@ -1366,7 +1366,13 @@ Image encryption (`EXT_ENCRYPTED`) needs `EXT_FLASH`, `MMU` or `CUSTOM_ENCRYPT_K ### Field-recoverable zeroize -`DISK_FAILOVER_ZEROIZE=1` (with `DISK_GOLDEN_SLOT=1`, `SNVM_KEK=1` and the `WOLFBOOT_SNVM_WRITE_APPROVED=1` gate every sNVM writer carries) turns a failover to the golden slot into a zeroize: when both operational slots are given up on, wolfBoot zeroes sNVM page `SNVM_ENCKEY_MODULE` (the PUF-wrapped image key, read back and checked; a page that already reads as zeros is not written again) and the head of both operational slots, then boots the golden image. The keystore page is never touched, so the golden image still verifies; it must be a plaintext signed image, since the key that decrypted the operational images is gone. Nothing derived from the PUF is lost: the MRAM log key is re-derived on every boot. Re-provisioning the image key afterwards is the `SNVM_ENCKEY_PROVISION` service flow; a brownout during the zeroize write can leave the page read-only, in which case the device cannot be re-keyed. +`DISK_FAILOVER_ZEROIZE=1` (with `DISK_GOLDEN_SLOT=1`, `SNVM_KEK=1` and the `WOLFBOOT_SNVM_WRITE_APPROVED=1` gate every sNVM writer carries) turns a failover to the golden slot into a zeroize: when both operational slots are given up on, wolfBoot zeroes sNVM page `SNVM_ENCKEY_MODULE` (the PUF-wrapped image key, read back and checked; a page that already reads as zeros is not written again) and the head of both operational slots, then boots the golden image. The keystore page is never touched, so the golden image still verifies; by default it must be a plaintext signed image, since the key that decrypted the operational images is gone. `DISK_GOLDEN_ENCRYPT_KEY=1` lifts that restriction - see below. Nothing derived from the PUF is lost: the MRAM log key is re-derived on every boot. Re-provisioning the image key afterwards is the `SNVM_ENCKEY_PROVISION` service flow; a brownout during the zeroize write can leave the page read-only, in which case the device cannot be re-keyed. + +#### Keeping the golden image encrypted + +`DISK_GOLDEN_ENCRYPT_KEY=1` (with `DISK_GOLDEN_SLOT=1` and `SNVM_KEK=1`) gives the golden image its own key in `SNVM_GOLDEN_ENCKEY_MODULE`, which the failover never erases. The zeroize still destroys the operational key in `SNVM_ENCKEY_MODULE`, so the operational images become undecryptable while the golden image stays encrypted at rest and bootable. Both keys are wrapped by the same device PUF KEK; the separation is which module gets erased. The platform picks the key with `wolfBoot_select_golden_encrypt_key()`, which has no default implementation on purpose: a target that cannot select a key fails to link rather than reading the golden image under the operational one. + +`SNVM_ENCKEY_PROVISION=1` writes both modules. Sign and encrypt the golden image with the golden-module key and the A/B images with the module-201 key; swapping them leaves the golden slot unbootable. `SNVM_GOLDEN_ENCKEY_MODULE` has no default and must be set: it has to name a plaintext sNVM client the Libero design both configures and leaves writable. A page marked ROM cannot be written, and a page never configured as a client cannot even be read, so the provisioning writer's pre-read refuses it. Use `MPFS_SNVM_DUMP` to check a candidate page first. On the Video Kit's current design only the keystore and image-key pages qualify, so a second key there needs a design change. With `SNVM_ENCKEY_PROVISION_EXTERN` the integrator supplies `snvm_golden_enckey_prov_key[32]` and `snvm_golden_enckey_prov_nonce[16]` alongside the operational pair. A golden bitstream that carries sNVM clients should carry the golden key module only: carrying module 201 as well restores the operational key on reapply and so partially undoes the zeroize. ### Build flags summary @@ -1381,6 +1387,8 @@ Image encryption (`EXT_ENCRYPTED`) needs `EXT_FLASH`, `MMU` or `CUSTOM_ENCRYPT_K | `SNVM_KEYSTORE_REQUIRE_ROM` | Refuse to boot unless every keystore page is ROM-flagged (bitstream-provisioned keystore) | | `MPFS_SCB_SMODE_DENY` | PMP-fence S-mode off the System Controller mailbox and disable its DTB nodes | | `DISK_FAILOVER_ZEROIZE` | Zero the image-key page and the operational slots when falling back to the golden image | +| `DISK_GOLDEN_ENCRYPT_KEY` | Give the golden image its own key in `SNVM_GOLDEN_ENCKEY_MODULE`, so it stays encrypted across a zeroize | +| `SNVM_GOLDEN_ENCKEY_MODULE` | sNVM module holding the golden image key (no default, required; must differ from `SNVM_ENCKEY_MODULE`, lie outside the keystore range, and be a writable plaintext client in the Libero design) | | `SNVM_ENCKEY_INSECURE_TEST_KEY` | Acknowledges provisioning the built-in public test key | | `SNVM_ENCKEY_PROVISION_EXTERN` | Provision a production key instead: the integrator defines `snvm_enckey_prov_key[32]` and `snvm_enckey_prov_nonce[16]` in another object | diff --git a/docs/encrypted_partitions.md b/docs/encrypted_partitions.md index 59b6c85e9f..64ccfbe414 100644 --- a/docs/encrypted_partitions.md +++ b/docs/encrypted_partitions.md @@ -42,7 +42,7 @@ You can use the `CUSTOM_ENCRYPT_KEY` option to implement your own functions for: `wolfBoot_get_encrypt_key`, `wolfBoot_set_encrypt_key` and `wolfBoot_erase_encrypt_key`. -For an example that stores the AES key in non-volatile memory wrapped by a device-unique hardware PUF key (Microchip PolarFire SoC), see [PolarFire.md](PolarFire.md#hardware-root-of-trust-puf-kek-snvm-keystore-wrapped-image-key). Note that disk/`NO_PARTITIONS` boot can use `CUSTOM_ENCRYPT_KEY` for image encryption without `EXT_FLASH` or `MMU`. +For an example that stores the AES key in non-volatile memory wrapped by a device-unique hardware PUF key (Microchip PolarFire SoC), see [PolarFire.md](PolarFire.md#hardware-root-of-trust-puf-kek-snvm-keystore-wrapped-image-key). Note that disk/`NO_PARTITIONS` boot can use `CUSTOM_ENCRYPT_KEY` for image encryption without `EXT_FLASH` or `MMU`. A disk-boot target with a golden slot can give the recovery image a key of its own, so it stays encrypted after the operational key is destroyed: see [Keeping the golden image encrypted](PolarFire.md#keeping-the-golden-image-encrypted). To enable: diff --git a/hal/mpfs250.c b/hal/mpfs250.c index 545565f5c3..48ae699561 100644 --- a/hal/mpfs250.c +++ b/hal/mpfs250.c @@ -40,7 +40,8 @@ #include "gpt.h" #include "fdt.h" #if defined(SNVM_KEYSTORE_PROVISION) || defined(SNVM_ENCKEY_PROVISION) || \ - defined(MPFS_SBI_VENDOR) || defined(MPFS_SNVM_DUMP) + defined(SNVM_GOLDEN_ENCKEY_PROVISION) || defined(MPFS_SBI_VENDOR) || \ + defined(MPFS_SNVM_DUMP) #include "mpfs250_snvm.h" #endif #if defined(MPFS_SBI_VENDOR) && defined(WOLFBOOT_MMODE_SMODE_BOOT) @@ -506,6 +507,12 @@ void hal_init(void) wolfBoot_panic(); } #endif +#ifdef SNVM_GOLDEN_ENCKEY_PROVISION + if (snvm_golden_enckey_provision() != 0) { + wolfBoot_printf("golden enckey provision: FAILED\n"); + wolfBoot_panic(); + } +#endif #ifdef EXT_FLASH if (qspi_init() != 0) { diff --git a/hal/mpfs250_snvm.c b/hal/mpfs250_snvm.c index d758df1f00..9c10cb2d43 100644 --- a/hal/mpfs250_snvm.c +++ b/hal/mpfs250_snvm.c @@ -40,7 +40,8 @@ #include #include /* wc_ForceZero */ -#if defined(SNVM_KEYSTORE_PROVISION) || defined(SNVM_ENCKEY_PROVISION) +#if defined(SNVM_KEYSTORE_PROVISION) || defined(SNVM_ENCKEY_PROVISION) || \ + defined(SNVM_GOLDEN_ENCKEY_PROVISION) /* Provisioning runs from hal_init() on every boot of a provisioning image, * and the programmer reboots the board once on its own, so a page that * already holds the content is left alone: sNVM pages have a write-cycle @@ -420,6 +421,22 @@ int snvm_kek_unwrap(const uint8_t *in, uint32_t insz, uint8_t *key, #error "SNVM PUF encrypt-key provider expects a 256-bit (AES-256) image key" #endif +#ifdef DISK_GOLDEN_ENCRYPT_KEY +/* Which module wolfBoot_get_encrypt_key() reads. Defaults to the + * operational one, so the A/B version probe that runs before any slot is + * elected needs no selection. */ +static uint8_t snvm_enckey_module = SNVM_ENCKEY_MODULE; + +int wolfBoot_select_golden_encrypt_key(int golden) +{ + snvm_enckey_module = golden ? SNVM_GOLDEN_ENCKEY_MODULE : + SNVM_ENCKEY_MODULE; + return 0; +} +#else +#define snvm_enckey_module SNVM_ENCKEY_MODULE +#endif + /* Read the wrapped AES key + nonce from sNVM, unwrap the key with the PUF KEK * and return both. Returns 0 on success. */ int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce) @@ -434,10 +451,11 @@ int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce) * or candidate key material in either output. */ wc_ForceZero(key, ENCRYPT_KEY_SIZE); wc_ForceZero(nonce, ENCRYPT_NONCE_SIZE); - ret = mpfs_snvm_read(SNVM_ENCKEY_MODULE, NULL, NULL, page, + ret = mpfs_snvm_read(snvm_enckey_module, NULL, NULL, page, MPFS_SNVM_PLAIN_DATA_LEN); if (ret != 0) { - wolfBoot_printf("encrypt-key: sNVM read failed (%d)\n", ret); + wolfBoot_printf("encrypt-key: sNVM read[%u] failed (%d)\n", + (unsigned)snvm_enckey_module, ret); return -1; } ret = snvm_kek_unwrap(page, SNVM_KEK_WRAPPED_LEN, key, ENCRYPT_KEY_SIZE); @@ -507,12 +525,13 @@ int wolfBoot_erase_encrypt_key(void) } #endif -#ifdef SNVM_ENCKEY_PROVISION +#if defined(SNVM_ENCKEY_PROVISION) || defined(SNVM_GOLDEN_ENCKEY_PROVISION) /* One-time: wrap the AES key with the device PUF KEK and store * [wrapped key][nonce] in sNVM. It must match the sign tool's key file. * The key comes from snvm_enckey_prov_key/nonce, defined by the integrator in * another object (SNVM_ENCKEY_PROVISION_EXTERN), or from the built-in public * test vector, which needs SNVM_ENCKEY_INSECURE_TEST_KEY as acknowledgement. */ +#ifdef SNVM_ENCKEY_PROVISION #if defined(SNVM_ENCKEY_PROVISION_EXTERN) extern const uint8_t snvm_enckey_prov_key[SNVM_KEK_AESKEY_LEN]; extern const uint8_t snvm_enckey_prov_nonce[ENCRYPT_NONCE_SIZE]; @@ -536,8 +555,36 @@ static const uint8_t prov_aes_nonce[ENCRYPT_NONCE_SIZE] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f }; #endif +#endif /* SNVM_ENCKEY_PROVISION */ -int snvm_enckey_provision(void) +#ifdef SNVM_GOLDEN_ENCKEY_PROVISION +#if defined(SNVM_ENCKEY_PROVISION_EXTERN) +extern const uint8_t snvm_golden_enckey_prov_key[SNVM_KEK_AESKEY_LEN]; +extern const uint8_t snvm_golden_enckey_prov_nonce[ENCRYPT_NONCE_SIZE]; +#define prov_golden_aes_key snvm_golden_enckey_prov_key +#define prov_golden_aes_nonce snvm_golden_enckey_prov_nonce +#elif !defined(SNVM_ENCKEY_INSECURE_TEST_KEY) +#error "SNVM_GOLDEN_ENCKEY_PROVISION writes a key into sNVM permanently. Define \ +SNVM_ENCKEY_PROVISION_EXTERN and provide snvm_golden_enckey_prov_key/nonce, or define \ +SNVM_ENCKEY_INSECURE_TEST_KEY to provision the built-in public test vector." +#else +/* Second publicly known test vector, distinct from the operational one so a + * test cannot pass by reading the wrong module. */ +static const uint8_t prov_golden_aes_key[SNVM_KEK_AESKEY_LEN] = { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f +}; +static const uint8_t prov_golden_aes_nonce[ENCRYPT_NONCE_SIZE] = { + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f +}; +#endif +#endif + +static int snvm_enckey_provision_module(uint8_t module, const uint8_t *key, + const uint8_t *nonce) { uint8_t page[MPFS_SNVM_PLAIN_DATA_LEN]; int ret, i; @@ -545,21 +592,40 @@ int snvm_enckey_provision(void) for (i = 0; i < (int)sizeof(page); i++) { page[i] = 0; } - ret = snvm_kek_wrap(prov_aes_key, (uint32_t)sizeof(prov_aes_key), page, + ret = snvm_kek_wrap(key, (uint32_t)SNVM_KEK_AESKEY_LEN, page, SNVM_KEK_WRAPPED_LEN); if (ret != SNVM_KEK_WRAPPED_LEN) { wolfBoot_printf("enckey provision: wrap failed (%d)\n", ret); wc_ForceZero(page, sizeof(page)); return -1; } - memcpy(page + SNVM_KEK_WRAPPED_LEN, prov_aes_nonce, ENCRYPT_NONCE_SIZE); - ret = snvm_page_write_if_changed(SNVM_ENCKEY_MODULE, page); + memcpy(page + SNVM_KEK_WRAPPED_LEN, nonce, ENCRYPT_NONCE_SIZE); + ret = snvm_page_write_if_changed(module, page); wolfBoot_printf("enckey provision: sNVM write[%d] ret=%d\n", - SNVM_ENCKEY_MODULE, ret); + (int)module, ret); wc_ForceZero(page, sizeof(page)); return ret; } -#endif /* SNVM_ENCKEY_PROVISION */ + +#ifdef SNVM_ENCKEY_PROVISION +int snvm_enckey_provision(void) +{ + return snvm_enckey_provision_module(SNVM_ENCKEY_MODULE, prov_aes_key, + prov_aes_nonce); +} +#endif + +/* Separate from snvm_enckey_provision() because the zeroize that makes a + * second key worth having cannot be built alongside it, while the golden + * module it writes is one the zeroize never touches. */ +#ifdef SNVM_GOLDEN_ENCKEY_PROVISION +int snvm_golden_enckey_provision(void) +{ + return snvm_enckey_provision_module(SNVM_GOLDEN_ENCKEY_MODULE, + prov_golden_aes_key, prov_golden_aes_nonce); +} +#endif +#endif /* SNVM_ENCKEY_PROVISION || SNVM_GOLDEN_ENCKEY_PROVISION */ #endif /* CUSTOM_ENCRYPT_KEY && SNVM_KEK */ diff --git a/hal/mpfs250_snvm.h b/hal/mpfs250_snvm.h index 32c335c24b..2be38c4f10 100644 --- a/hal/mpfs250_snvm.h +++ b/hal/mpfs250_snvm.h @@ -115,6 +115,38 @@ int snvm_keystore_provision(void); #endif #endif +#if defined(DISK_GOLDEN_ENCRYPT_KEY) || defined(SNVM_GOLDEN_ENCKEY_PROVISION) +/* sNVM module holding the golden image's own wrapped key. The failover + * never erases it, which is what keeps the golden image encrypted after a + * zeroize of SNVM_ENCKEY_MODULE. + * + * There is deliberately no default: the page must be a plaintext sNVM client + * the Libero design actually configures AND leaves writable, and only the + * board's design says which that is. A page marked ROM cannot be written, + * and a page that was never configured as a client cannot even be read, so + * the provisioning writer's pre-read refuses it. Both failures are silent + * until a device is in hand, which is why this is a required setting rather + * than a guess. MPFS_SNVM_DUMP reports the state of a candidate page. */ +#ifndef SNVM_GOLDEN_ENCKEY_MODULE +#error "Set SNVM_GOLDEN_ENCKEY_MODULE to a writable plaintext sNVM client \ +configured by the Libero design; see MPFS_SNVM_DUMP to check a candidate page." +#endif +#if (SNVM_GOLDEN_ENCKEY_MODULE < 0) || \ + (SNVM_GOLDEN_ENCKEY_MODULE >= MPFS_SNVM_MODULE_MAX) +#error "SNVM_GOLDEN_ENCKEY_MODULE is outside the sNVM module range 0..220" +#endif +#if (SNVM_GOLDEN_ENCKEY_MODULE == SNVM_ENCKEY_MODULE) +#error "SNVM_GOLDEN_ENCKEY_MODULE must differ from SNVM_ENCKEY_MODULE" +#endif +#if defined(SNVM_KEYSTORE) || defined(SNVM_KEYSTORE_PROVISION) +#if (SNVM_GOLDEN_ENCKEY_MODULE >= SNVM_KEYSTORE_MODULE) && \ + (SNVM_GOLDEN_ENCKEY_MODULE < \ + (SNVM_KEYSTORE_MODULE + SNVM_KEYSTORE_MAX_MODULES)) +#error "SNVM_GOLDEN_ENCKEY_MODULE overlaps the sNVM keystore module range" +#endif +#endif +#endif /* DISK_GOLDEN_ENCRYPT_KEY || SNVM_GOLDEN_ENCKEY_PROVISION */ + /* Derive the 256-bit device-unique KEK from the System Controller SRAM-PUF: * KEK = SHA384(label || PUF(fixed-challenge))[0..31]. Returns 0 on success. */ int mpfs_puf_kek(uint8_t *kek); @@ -135,6 +167,10 @@ int snvm_kek_unwrap(const uint8_t *in, uint32_t insz, uint8_t *key, * the PUF KEK and store [wrapped key][nonce] in sNVM. */ int snvm_enckey_provision(void); #endif +#ifdef SNVM_GOLDEN_ENCKEY_PROVISION +/* The same for the golden image's key, into SNVM_GOLDEN_ENCKEY_MODULE. */ +int snvm_golden_enckey_provision(void); +#endif #endif /* SNVM_KEK */ diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index 65824e6dcf..a5740078e2 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -903,6 +903,12 @@ int wolfBoot_initialize_encryption(void); int wolfBoot_set_encrypt_key(const uint8_t *key, const uint8_t *nonce); int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce); int wolfBoot_erase_encrypt_key(void); +#ifdef DISK_GOLDEN_ENCRYPT_KEY +/* Select the golden image's key (golden != 0) or the operational one for the + * next wolfBoot_get_encrypt_key(). Implemented by the platform that holds + * both; wolfBoot_erase_encrypt_key() always erases the operational key. */ +int wolfBoot_select_golden_encrypt_key(int golden); +#endif #if !defined(__WOLFBOOT) && defined(TZEN) diff --git a/options.mk b/options.mk index 9335dd633b..96a1989186 100644 --- a/options.mk +++ b/options.mk @@ -119,6 +119,9 @@ endif ifneq ($(SNVM_KEYSTORE_MAX_MODULES),) CFLAGS+=-DSNVM_KEYSTORE_MAX_MODULES=$(SNVM_KEYSTORE_MAX_MODULES) endif +ifneq ($(SNVM_GOLDEN_ENCKEY_MODULE),) + CFLAGS+=-DSNVM_GOLDEN_ENCKEY_MODULE=$(SNVM_GOLDEN_ENCKEY_MODULE) +endif ifneq ($(SNVM_ENCKEY_MODULE),) CFLAGS+=-DSNVM_ENCKEY_MODULE=$(SNVM_ENCKEY_MODULE) endif @@ -180,7 +183,8 @@ ifeq ($(MPFS_SBI_SC_FLASH),1) endif # Field recovery: before the golden slot boots, zero the sNVM image-key page and the head of both -# operational slots (hal_disk_failover). The golden image must be a plaintext signed image. +# operational slots (hal_disk_failover). The golden image must be a plaintext signed image, unless +# DISK_GOLDEN_ENCRYPT_KEY gives it a key of its own. ifeq ($(DISK_FAILOVER_ZEROIZE),1) ifneq ($(DISK_GOLDEN_SLOT)$(SNVM_KEK),11) $(error DISK_FAILOVER_ZEROIZE=1 requires DISK_GOLDEN_SLOT=1 and SNVM_KEK=1) @@ -194,6 +198,38 @@ ifeq ($(DISK_FAILOVER_ZEROIZE),1) endif CFLAGS+=-D"DISK_FAILOVER_ZEROIZE" endif + +# Keep the golden image encrypted across a zeroize by storing its image key in a second sNVM +# module (SNVM_GOLDEN_ENCKEY_MODULE), which the failover never erases. The platform must +# implement wolfBoot_select_golden_encrypt_key(); there is deliberately no default, so a build +# that cannot select a key fails to link rather than decrypting the golden image with the wrong one. +# One-time writer for the golden image's key. Deliberately independent of +# DISK_GOLDEN_ENCRYPT_KEY so a provisioning-only build can populate the second module: +# a provisioning run cannot be built together with DISK_FAILOVER_ZEROIZE, and the module +# written here is one the zeroize never touches. Provision first, then build the runtime +# image. +ifeq ($(SNVM_GOLDEN_ENCKEY_PROVISION),1) + ifneq ($(SNVM_KEK),1) + $(error SNVM_GOLDEN_ENCKEY_PROVISION=1 requires SNVM_KEK=1) + endif + ifneq ($(WOLFBOOT_SNVM_WRITE_APPROVED),1) + $(error SNVM_GOLDEN_ENCKEY_PROVISION $(SNVM_WRITE_GATE_MSG)) + endif + CFLAGS+=-D"SNVM_GOLDEN_ENCKEY_PROVISION" + ifeq ($(SNVM_ENCKEY_INSECURE_TEST_KEY),1) + CFLAGS+=-D"SNVM_ENCKEY_INSECURE_TEST_KEY" + endif + ifeq ($(SNVM_ENCKEY_PROVISION_EXTERN),1) + CFLAGS+=-D"SNVM_ENCKEY_PROVISION_EXTERN" + endif +endif + +ifeq ($(DISK_GOLDEN_ENCRYPT_KEY),1) + ifneq ($(DISK_GOLDEN_SLOT)$(SNVM_KEK),11) + $(error DISK_GOLDEN_ENCRYPT_KEY=1 requires DISK_GOLDEN_SLOT=1 and SNVM_KEK=1) + endif + CFLAGS+=-D"DISK_GOLDEN_ENCRYPT_KEY" +endif # QSPI SCK divider: a QSPI_CLK_DIV_* name from hal/mpfs250.h, default QSPI_CLK_DIV_8. ifneq ($(MPFS_QSPI_CLK_DIV),) CFLAGS+=-DMPFS_QSPI_CLK_DIV=$(MPFS_QSPI_CLK_DIV) diff --git a/src/update_disk.c b/src/update_disk.c index 815969ad67..50e37f9359 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -179,7 +179,13 @@ static int failover_cause; failover_cause = (c); } } while (0) static int disk_key_ok = 1; #define SLOT_ENCRYPTED_KEY_OK() (disk_key_ok != 0) +#ifdef DISK_GOLDEN_ENCRYPT_KEY +/* The golden image has a key of its own, so it stays encrypted after the + * operational one is zeroized. */ +#define SLOT_ENCRYPTED(s) 1 +#else #define SLOT_ENCRYPTED(s) (!SLOT_IS_GOLDEN(s)) +#endif /* Bytes zeroed at the start of each operational slot: the manifest header * and the signature go, so the image can never be elected again. */ #ifndef DISK_FAILOVER_ERASE_SIZE @@ -341,6 +347,21 @@ static void disk_crypto_clear(void) wc_ForceZero(disk_encrypt_nonce, sizeof(disk_encrypt_nonce)); } +#ifdef DISK_GOLDEN_ENCRYPT_KEY +/* Re-key the cipher and the IV nonce for the slot about to be read. Both are + * keyed once up front, which is enough only while every slot shares a key. */ +static int disk_crypto_rekey(int slot) +{ + if (wolfBoot_select_golden_encrypt_key(SLOT_IS_GOLDEN(slot)) != 0) { + return -1; + } + if (crypto_init() != 0) { + return -1; + } + return wolfBoot_get_encrypt_key(disk_encrypt_key, disk_encrypt_nonce); +} +#endif + static void disk_decrypted_header_clear(uint8_t *hdr) { wc_ForceZero(hdr, IMAGE_HEADER_SIZE); @@ -1068,8 +1089,18 @@ void RAMFUNCTION wolfBoot_start(void) hdr_ptr = p_hdr; #ifdef DISK_ENCRYPT - /* Decrypt header to parse image size. A golden image is signed - * but not encrypted: it must boot after the image key is gone. */ +#ifdef DISK_GOLDEN_ENCRYPT_KEY + /* Each slot carries its own key, so re-key before reading one. */ + if (disk_crypto_rekey(selected) != 0) { + wolfBoot_printf("Error selecting the key for %s\r\n", part_name); + SET_FAILOVER_IF_UNSET(DISK_FAILOVER_NO_KEY); + selected ^= 1; + continue; + } +#endif + /* Decrypt header to parse image size. Without + * DISK_GOLDEN_ENCRYPT_KEY a golden image is signed but not + * encrypted: it must boot after the image key is gone. */ if (!SLOT_ENCRYPTED(selected)) { hdr_ptr = p_hdr; } diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 7bae2327bd..88c8009abe 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -80,7 +80,7 @@ TESTS:=unit-parser unit-parser-large-header unit-fdt unit-extflash unit-string \ unit-update-flash-self-update-monolithic \ unit-nsc-update \ unit-update-flash-enc unit-update-flash-enc-full unit-update-flash-enc-intswap unit-update-ram unit-update-ram-fit unit-update-ram-uboot unit-update-ram-enc unit-update-ram-enc-nopart unit-update-ram-nofixed unit-update-ram-nofixed-noramboot unit-update-ram-noramboot unit-update-ram-custom-trailer unit-compressed-tlv unit-custom-trailer-nopart unit-update-flash-hwswap unit-update-flash-custom-trailer unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \ - unit-update-disk unit-update-disk-confirm unit-update-disk-golden unit-update-disk-failover unit-update-disk-fsp unit-update-disk-oob unit-update-disk-fit unit-update-disk-fit-ramdisk unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ + unit-update-disk unit-update-disk-confirm unit-update-disk-golden unit-update-disk-failover unit-update-disk-golden-enckey unit-update-disk-fsp unit-update-disk-oob unit-update-disk-fit unit-update-disk-fit-ramdisk unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-image-dts \ unit-image-dts-sha384 unit-image-dts-sha3-384 unit-store-sbrk \ unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \ @@ -125,6 +125,7 @@ TESTS+=unit-flash-m2354 TESTS+=unit-otp-keystore TESTS+=unit-snvm-keystore TESTS+=unit-snvm-kek +TESTS+=unit-snvm-kek-golden TESTS+=unit-otp-keystore-gen-zeroize TESTS+=unit-x86-paging-oob TESTS+=unit-ahci-unlock-panic @@ -549,6 +550,8 @@ unit-update-disk-golden:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RA -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-disk-failover:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RAMBOOT_MAX_SIZE=0x400 \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +unit-update-disk-golden-enckey:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RAMBOOT_MAX_SIZE=0x400 \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE # Non-FSP disk-boot OOB regression (CRIT-03). WOLFBOOT_RAMBOOT_MAX_SIZE is the # cap the loader applies to the unauthenticated header fw_size before loading to # RAM, the same bound update_disk.c and update_ram.c enforce. @@ -938,6 +941,21 @@ unit-snvm-kek: unit-snvm-kek.c ../../hal/mpfs250_snvm.c -DENCRYPT_WITH_AES256 -DSNVM_ENCKEY_PROVISION \ -DSNVM_ENCKEY_INSECURE_TEST_KEY $(CFLAGS) $(LDFLAGS) +# The same provider built with the two-key layout, so the real selector, getter +# and erase are exercised rather than the disk suite's mocks. +unit-snvm-kek-golden: unit-snvm-kek.c ../../hal/mpfs250_snvm.c + gcc -o $@ unit-snvm-kek.c -I../.. \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/aes.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha512.c \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c \ + -DSNVM_KEK -DWOLFBOOT_ENABLE_HASH_SHA384 -DWOLFSSL_AES_256 \ + -DHAVE_AES_KEYWRAP -DWOLFSSL_AES_DIRECT -DHAVE_AES_ECB \ + -DHAVE_AES_DECRYPT -DCUSTOM_ENCRYPT_KEY -DEXT_ENCRYPTED \ + -DENCRYPT_WITH_AES256 -DSNVM_ENCKEY_PROVISION \ + -DDISK_GOLDEN_ENCRYPT_KEY -DSNVM_GOLDEN_ENCKEY_PROVISION \ + -DSNVM_GOLDEN_ENCKEY_MODULE=199 -DDISK_FAILOVER_ZEROIZE \ + -DSNVM_ENCKEY_INSECURE_TEST_KEY $(CFLAGS) $(LDFLAGS) + # unit-otp-keystore-gen-zeroize includes the host tool # tools/keytools/otp/otp-keystore-gen.c directly (via #define main) and # interposes read()/malloc()/free() (dlsym RTLD_NEXT, hence -ldl) to observe @@ -1411,6 +1429,9 @@ unit-update-disk-golden: ../../include/target.h unit-update-disk-golden.c unit-update-disk-failover: ../../include/target.h unit-update-disk-failover.c gcc -o $@ unit-update-disk-failover.c $(CFLAGS) $(LDFLAGS) +unit-update-disk-golden-enckey: ../../include/target.h unit-update-disk-golden-enckey.c + gcc -o $@ unit-update-disk-golden-enckey.c $(CFLAGS) $(LDFLAGS) + # WOLFBOOT_FSP (x86) boot path of update_disk.c: the low-memory (tolum) # size check must reject a slot and fall back to the other one, like every # other per-slot rejection in the retry loop. diff --git a/tools/unit-tests/unit-snvm-kek.c b/tools/unit-tests/unit-snvm-kek.c index 17cb8f1880..c139eb51c6 100644 --- a/tools/unit-tests/unit-snvm-kek.c +++ b/tools/unit-tests/unit-snvm-kek.c @@ -250,11 +250,56 @@ START_TEST(test_set_erase_fail_closed) memset(key, 0x11, sizeof(key)); memset(nonce, 0x22, sizeof(nonce)); ck_assert_int_eq(wolfBoot_set_encrypt_key(key, nonce), -1); +#ifndef DISK_FAILOVER_ZEROIZE + /* Without the zeroize the erase is a fail-closed stub. The real one is + * built in only with DISK_FAILOVER_ZEROIZE and is covered below. */ ck_assert_int_eq(wolfBoot_erase_encrypt_key(), -1); +#endif ck_assert_int_eq(mock_snvm_writes, 0); } END_TEST +#if defined(DISK_GOLDEN_ENCRYPT_KEY) && defined(SNVM_GOLDEN_ENCKEY_PROVISION) +/* The disk suite mocks the selector, the getter and the erase, so only this + * one exercises the real provider: that the two modules hold different keys, + * that the selector decides which is served, and that the erase takes the + * operational page even with golden selected. */ +START_TEST(test_two_key_selection_and_golden_survives_erase) +{ + uint8_t key[ENCRYPT_KEY_SIZE], nonce[ENCRYPT_NONCE_SIZE]; + uint8_t op_key[ENCRYPT_KEY_SIZE], golden_key[ENCRYPT_KEY_SIZE]; + static const uint8_t zeros[MPFS_SNVM_PLAIN_DATA_LEN]; + + reset_mocks(); + ck_assert_int_eq(snvm_enckey_provision(), 0); + ck_assert_int_eq(snvm_golden_enckey_provision(), 0); + /* Distinct wrapped blobs, so a test cannot pass by reading either page. */ + ck_assert_int_ne(memcmp(mock_snvm[SNVM_ENCKEY_MODULE], + mock_snvm[SNVM_GOLDEN_ENCKEY_MODULE], MPFS_SNVM_PLAIN_DATA_LEN), 0); + + ck_assert_int_eq(wolfBoot_select_golden_encrypt_key(0), 0); + ck_assert_int_eq(wolfBoot_get_encrypt_key(op_key, nonce), 0); + ck_assert_int_eq(wolfBoot_select_golden_encrypt_key(1), 0); + ck_assert_int_eq(wolfBoot_get_encrypt_key(golden_key, nonce), 0); + ck_assert_int_ne(memcmp(op_key, golden_key, sizeof(op_key)), 0); + + /* Golden is still selected here: the erase must ignore that. */ + ck_assert_int_eq(wolfBoot_erase_encrypt_key(), 0); + ck_assert_int_eq(memcmp(mock_snvm[SNVM_ENCKEY_MODULE], zeros, + MPFS_SNVM_PLAIN_DATA_LEN), 0); + ck_assert_int_ne(memcmp(mock_snvm[SNVM_GOLDEN_ENCKEY_MODULE], zeros, + MPFS_SNVM_PLAIN_DATA_LEN), 0); + + /* Golden still unwraps to the same key; the operational one is gone. */ + ck_assert_int_eq(wolfBoot_select_golden_encrypt_key(1), 0); + ck_assert_int_eq(wolfBoot_get_encrypt_key(key, nonce), 0); + ck_assert_int_eq(memcmp(key, golden_key, sizeof(key)), 0); + ck_assert_int_eq(wolfBoot_select_golden_encrypt_key(0), 0); + ck_assert_int_ne(wolfBoot_get_encrypt_key(key, nonce), 0); +} +END_TEST +#endif + Suite *snvm_kek_suite(void) { Suite *s = suite_create("snvm-kek"); @@ -267,6 +312,9 @@ Suite *snvm_kek_suite(void) tcase_add_test(tc, test_provision_then_get_encrypt_key); tcase_add_test(tc, test_get_encrypt_key_failures); tcase_add_test(tc, test_set_erase_fail_closed); +#if defined(DISK_GOLDEN_ENCRYPT_KEY) && defined(SNVM_GOLDEN_ENCKEY_PROVISION) + tcase_add_test(tc, test_two_key_selection_and_golden_survives_erase); +#endif suite_add_tcase(s, tc); return s; diff --git a/tools/unit-tests/unit-update-disk-golden-enckey.c b/tools/unit-tests/unit-update-disk-golden-enckey.c new file mode 100644 index 0000000000..ac5ec8d38c --- /dev/null +++ b/tools/unit-tests/unit-update-disk-golden-enckey.c @@ -0,0 +1,442 @@ +/* unit-update-disk-golden-enckey.c + * + * Unit test for a golden slot encrypted under its own key + * (DISK_GOLDEN_ENCRYPT_KEY), so a zeroize of the operational key leaves the + * recovery image both encrypted and bootable. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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 3 of the License, or + * (at your option) any later version. + * + * wolfBoot 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define WOLFBOOT_UPDATE_DISK +#define WOLFBOOT_SELF_UPDATE_MONOLITHIC +#define RAM_CODE +#define WOLFBOOT_SELF_HEADER +#define EXT_ENCRYPTED +#define ENCRYPT_WITH_CHACHA +#define HAVE_CHACHA +#define IMAGE_HEADER_SIZE 256 +#define BOOT_PART_A 0 +#define BOOT_PART_B 1 +#define BOOT_PART_GOLDEN 2 +#define DISK_GOLDEN_SLOT +#define DISK_FAILOVER_ZEROIZE +#define DISK_GOLDEN_ENCRYPT_KEY +#define MOCK_ADDRESS_BOOT 0xCD000000 + +#include +#include +#include +#include + +#include "hal.h" +#include "target.h" +#include "wolfboot/wolfboot.h" +#include "image.h" +#include "loader.h" +#include + +#define TEST_PAYLOAD_SIZE 64 +#define TEST_PART_SIZE (IMAGE_HEADER_SIZE + TEST_PAYLOAD_SIZE + 1024) +#define FILL_A 0xA1 +#define FILL_B 0xB2 +#define FILL_G 0xC3 +#define N_PARTS 3 + +/* The two image keys. The cipher mock below is an XOR with the installed key + * byte, so a slot read under the wrong key yields a wrong magic and a wrong + * payload fill: key separation is actually exercised, not just recorded. */ +#define OP_KEY_BYTE 0x5A +#define GOLDEN_KEY_BYTE 0x77 + +/* The region of a partition that is encrypted; the trailer is not. */ +#define ENC_LEN (IMAGE_HEADER_SIZE + TEST_PAYLOAD_SIZE) + +static uint8_t load_buffer[TEST_PART_SIZE]; +#define WOLFBOOT_LOAD_ADDRESS ((uintptr_t)load_buffer) + +static uint8_t part_image[N_PARTS][TEST_PART_SIZE]; +static int mock_reads[N_PARTS]; +static int mock_writes[N_PARTS]; +static int mock_bad_fill[N_PARTS]; +static int mock_do_boot_called; +static int mock_booted_fill; +static int mock_op_key_gone; +static int mock_golden_key_gone; +static int mock_op_key_erased; +static int mock_golden_key_erased; +static int mock_golden_selected; +static uint8_t mock_pending_key; +static uint8_t mock_cur_key; +ChaCha chacha; + +static void set_u16_le(uint8_t *dst, uint16_t value) +{ + dst[0] = (uint8_t)(value & 0xFF); + dst[1] = (uint8_t)(value >> 8); +} + +static void set_u32_le(uint8_t *dst, uint32_t value) +{ + dst[0] = (uint8_t)(value & 0xFF); + dst[1] = (uint8_t)((value >> 8) & 0xFF); + dst[2] = (uint8_t)((value >> 16) & 0xFF); + dst[3] = (uint8_t)(value >> 24); +} + +/* Build a plaintext image, then "encrypt" the image region under key_byte. */ +static void build_image(uint8_t *image, uint32_t version, uint8_t fill, + uint8_t key_byte) +{ + int i; + + memset(image, 0, TEST_PART_SIZE); + set_u32_le(image, WOLFBOOT_MAGIC); + set_u32_le(image + sizeof(uint32_t), TEST_PAYLOAD_SIZE); + set_u16_le(image + IMAGE_HEADER_OFFSET, HDR_VERSION); + set_u16_le(image + IMAGE_HEADER_OFFSET + sizeof(uint16_t), 4); + set_u32_le(image + IMAGE_HEADER_OFFSET + 2 * sizeof(uint16_t), version); + memset(image + IMAGE_HEADER_SIZE, fill, TEST_PAYLOAD_SIZE); + for (i = 0; i < ENC_LEN; i++) { + image[i] ^= key_byte; + } +} + +static void reset_mocks(void) +{ + memset(load_buffer, 0, sizeof(load_buffer)); + build_image(part_image[0], 3, FILL_A, OP_KEY_BYTE); + build_image(part_image[1], 2, FILL_B, OP_KEY_BYTE); + build_image(part_image[2], 1, FILL_G, GOLDEN_KEY_BYTE); + memset(mock_reads, 0, sizeof(mock_reads)); + memset(mock_writes, 0, sizeof(mock_writes)); + memset(mock_bad_fill, 0, sizeof(mock_bad_fill)); + mock_do_boot_called = 0; + mock_booted_fill = -1; + mock_op_key_gone = 0; + mock_golden_key_gone = 0; + mock_op_key_erased = 0; + mock_golden_key_erased = 0; + mock_golden_selected = -1; + mock_pending_key = OP_KEY_BYTE; + mock_cur_key = OP_KEY_BYTE; + wolfBoot_panicked = 0; +} + +static void mark_bad(uint8_t fill) +{ + int i; + for (i = 0; i < N_PARTS; i++) { + if (mock_bad_fill[i] == 0) { + mock_bad_fill[i] = fill; + return; + } + } +} + +static int pending_key_gone(void) +{ + if (mock_pending_key == GOLDEN_KEY_BYTE) { + return mock_golden_key_gone; + } + return mock_op_key_gone; +} + +/* crypto_init() expands to this: it installs the selected key, the way the + * real one re-reads it from storage. */ +int chacha_init(void) +{ + if (pending_key_gone()) { + return -1; + } + mock_cur_key = mock_pending_key; + return 0; +} + +int wc_Chacha_SetIV(ChaCha* ctx, const byte* inIv, word32 counter) +{ + (void)ctx; (void)inIv; (void)counter; + return 0; +} + +int wc_Chacha_Process(ChaCha* ctx, byte* output, const byte* input, + word32 msglen) +{ + word32 i; + (void)ctx; + for (i = 0; i < msglen; i++) { + output[i] = (byte)(input[i] ^ mock_cur_key); + } + return 0; +} + +void wc_ForceZero(void* mem, size_t len) +{ + volatile uint8_t *p = (volatile uint8_t *)mem; + while (len-- > 0) + *p++ = 0; +} + +int wolfBoot_initialize_encryption(void) +{ + return chacha_init(); +} + +int wolfBoot_select_golden_encrypt_key(int golden) +{ + mock_golden_selected = golden; + mock_pending_key = golden ? GOLDEN_KEY_BYTE : OP_KEY_BYTE; + return 0; +} + +/* Returns key material only. Installing it into the cipher is chacha_init()'s + * job, exactly as in production: if this installed the key too, a + * disk_crypto_rekey() that forgot crypto_init() would still pass. */ +int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce) +{ + if (pending_key_gone()) { + return -1; + } + memset(key, mock_pending_key, ENCRYPT_KEY_SIZE); + memset(nonce, 0xC3, ENCRYPT_NONCE_SIZE); + return 0; +} + +/* The failover only ever erases the operational key. The golden counter is + * here so a regression that erased it would fail the suite. */ +int wolfBoot_erase_encrypt_key(void) +{ + mock_op_key_erased++; + mock_op_key_gone = 1; + return 0; +} + +int disk_init(int drv) +{ + (void)drv; + return 0; +} + +int disk_open(int drv) +{ + (void)drv; + return 0; +} + +void disk_close(int drv) +{ + (void)drv; +} + +int disk_part_read(int drv, int part, uint64_t off, uint64_t sz, uint8_t *buf) +{ + (void)drv; + if (part < 0 || part >= N_PARTS) + return -1; + if ((off > TEST_PART_SIZE) || (sz > (TEST_PART_SIZE - off))) + return -1; + mock_reads[part]++; + memcpy(buf, part_image[part] + off, (size_t)sz); + return (int)sz; +} + +int disk_part_write(int drv, int part, uint64_t off, uint64_t sz, + const uint8_t *buf) +{ + (void)drv; + if (part < 0 || part >= N_PARTS) + return -1; + if ((off > TEST_PART_SIZE) || (sz > (TEST_PART_SIZE - off))) + return -1; + mock_writes[part]++; + memcpy(part_image[part] + off, buf, (size_t)sz); + return (int)sz; +} + +int disk_part_size(int drv, int part, uint64_t *size) +{ + (void)drv; + if (part < 0 || part >= N_PARTS) + return -1; + *size = TEST_PART_SIZE; + return 0; +} + +int wolfBoot_open_image_address(struct wolfBoot_image* img, uint8_t* image) +{ + uint32_t magic; + uint32_t fw_size; + + memcpy(&magic, image, sizeof(magic)); + if (magic != WOLFBOOT_MAGIC) + return -1; + memset(img, 0, sizeof(*img)); + img->hdr = image; + memcpy(&fw_size, image + sizeof(uint32_t), sizeof(fw_size)); + img->fw_size = fw_size; + img->fw_base = image + IMAGE_HEADER_SIZE; + img->hdr_ok = 1; + return 0; +} + +int wolfBoot_verify_integrity(struct wolfBoot_image* img) +{ + int i; + uint8_t fill = load_buffer[0]; + (void)img; + for (i = 0; i < N_PARTS; i++) { + if (mock_bad_fill[i] != 0 && mock_bad_fill[i] == fill) + return -1; + } + img->sha_ok = 1; + return 0; +} + +int wolfBoot_verify_authenticity(struct wolfBoot_image* img) +{ + img->signature_ok = 1; + return 0; +} + +int wolfBoot_image_part_matches(struct wolfBoot_image* img) +{ + (void)img; + return 0; +} + +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) +{ + (void)capacity; (void)dts_addr; + return -1; +} + +void hal_prepare_boot(void) +{ +} + +void do_boot(const uint32_t *address) +{ + mock_do_boot_called++; + mock_booted_fill = ((const uint8_t*)address)[0]; +} + +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; (void)len; + return 0; +} + +#include "update_disk.c" + +START_TEST(test_operational_boots_under_its_own_key) +{ + reset_mocks(); + wolfBoot_start(); + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_booted_fill, FILL_A); + ck_assert_int_eq(mock_op_key_erased, 0); + ck_assert_int_eq(mock_golden_key_erased, 0); +} +END_TEST + +START_TEST(test_encrypted_golden_boots_after_the_zeroize) +{ + /* The case that is impossible without this option: both operational + * images are given up on, the failover zeroizes their key, and the + * golden image still decrypts because its key is a different one. */ + reset_mocks(); + build_image(part_image[1], 3, FILL_B, OP_KEY_BYTE); + mark_bad(FILL_A); + mark_bad(FILL_B); + wolfBoot_start(); + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_booted_fill, FILL_G); + ck_assert_int_eq(mock_op_key_erased, 1); + ck_assert_int_eq(mock_golden_selected, 1); + ck_assert_int_eq(mock_writes[BOOT_PART_GOLDEN], 0); +} +END_TEST + +START_TEST(test_failover_leaves_the_golden_key_alone) +{ + reset_mocks(); + memset(part_image[0], 0, TEST_PART_SIZE); + memset(part_image[1], 0, TEST_PART_SIZE); + wolfBoot_start(); + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_booted_fill, FILL_G); + ck_assert_int_eq(mock_op_key_erased, 1); + /* The whole point: the recovery key survives the zeroize. */ + ck_assert_int_eq(mock_golden_key_erased, 0); + ck_assert_int_eq(mock_golden_key_gone, 0); +} +END_TEST + +START_TEST(test_golden_fails_closed_without_its_own_key) +{ + /* No silent plaintext fallback: with the golden key gone there is + * nothing left to boot. */ + reset_mocks(); + mock_op_key_gone = 1; + mock_golden_key_gone = 1; + wolfBoot_start(); + ck_assert_int_eq(mock_do_boot_called, 0); + ck_assert_int_eq(wolfBoot_panicked, 1); +} +END_TEST + +START_TEST(test_golden_under_the_operational_key_does_not_boot) +{ + /* A golden image encrypted with the operational key must not boot once + * the golden slot is read under the golden key. */ + reset_mocks(); + build_image(part_image[2], 1, FILL_G, OP_KEY_BYTE); + memset(part_image[0], 0, TEST_PART_SIZE); + memset(part_image[1], 0, TEST_PART_SIZE); + wolfBoot_start(); + ck_assert_int_eq(mock_do_boot_called, 0); + ck_assert_int_eq(wolfBoot_panicked, 1); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("wolfBoot"); + TCase *tc = tcase_create("update-disk-golden-enckey"); + + tcase_add_test(tc, test_operational_boots_under_its_own_key); + tcase_add_test(tc, test_encrypted_golden_boots_after_the_zeroize); + tcase_add_test(tc, test_failover_leaves_the_golden_key_alone); + tcase_add_test(tc, test_golden_fails_closed_without_its_own_key); + tcase_add_test(tc, test_golden_under_the_operational_key_does_not_boot); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = wolfboot_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +}