Repository: nrfconnect/sdk-mcuboot
Pull request #404: key revocation
Author: login: michalek-no
Created: 2025-03-17T20:29:07Z | Merged: 2025-04-03T08:02:50Z

Disable previous generation key when update comes with
new valid key and application is confirmed


Diff:
diff --git a/boot/bootutil/include/bootutil/key_revocation.h b/boot/bootutil/include/bootutil/key_revocation.h
new file mode 100644
index 0000000000..d184c95795
--- /dev/null
+++ b/boot/bootutil/include/bootutil/key_revocation.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2025 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
+ */
+
+#ifndef H_KEY_REVOCATION_
+#define H_KEY_REVOCATION_
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BOOT_KEY_REVOKE_OK 0
+#define BOOT_KEY_REVOKE_NOT_READY 1
+#define BOOT_KEY_REVOKE_INVALID 2
+#define BOOT_KEY_REVOKE_FAILED 2
+
+
+void allow_revoke(void);
+
+int revoke(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/boot/bootutil/src/ed25519_psa.c b/boot/bootutil/src/ed25519_psa.c
index 3e9cf2cbd6..c94d99e61e 100644
--- a/boot/bootutil/src/ed25519_psa.c
+++ b/boot/bootutil/src/ed25519_psa.c
@@ -32,6 +32,11 @@ static psa_key_id_t kmu_key_ids[3] =  {
     MAKE_PSA_KMU_KEY_ID(230)
 };
 
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+#include <bootutil/key_revocation.h>
+static psa_key_id_t *validated_with = NULL;
+#endif
+
 BUILD_ASSERT(CONFIG_BOOT_SIGNATURE_KMU_SLOTS <= ARRAY_SIZE(kmu_key_ids),
 	     "Invalid number of KMU slots, up to 3 are supported on nRF54L15");
 #endif
@@ -114,6 +119,9 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
                                     EDDSA_SIGNAGURE_LENGTH);
         if (status == PSA_SUCCESS) {
             ret = 1;
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+            validated_with = kmu_key_ids + i;
+#endif
             break;
         }
 
@@ -122,4 +130,37 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
 
     return ret;
 }
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+int exec_revoke(void)
+{
+    int ret = BOOT_KEY_REVOKE_OK;
+    psa_status_t status = psa_crypto_init();
+
+    if (!validated_with) {
+        ret = BOOT_KEY_REVOKE_INVALID;
+        goto out;
+    }
+
+    if (status != PSA_SUCCESS) {
+            BOOT_LOG_ERR("PSA crypto init failed with error %d", status);
+            ret = BOOT_KEY_REVOKE_FAILED;
+            goto out;
+    }
+    for (int i = 0; i < CONFIG_BOOT_SIGNATURE_KMU_SLOTS; i++) {
+        if ((kmu_key_ids + i) == validated_with) {
+            break;
+        }
+        BOOT_LOG_DBG("Invalidating key ID %d", i);
+
+        status = psa_destroy_key(kmu_key_ids[i]);
+        if (status == PSA_SUCCESS) {
+            BOOT_LOG_DBG("Success on key ID %d", i);
+        } else {
+            BOOT_LOG_ERR("Key invalidation failed with: %d", status);
+        }
+    }
+out:
+    return ret;
+}
+#endif /* CONFIG_BOOT_KMU_KEYS_REVOCATION */
 #endif
diff --git a/boot/bootutil/src/key_revocation.c b/boot/bootutil/src/key_revocation.c
new file mode 100644
index 0000000000..0768a31885
--- /dev/null
+++ b/boot/bootutil/src/key_revocation.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
+ */
+
+#include <bootutil/key_revocation.h>
+
+extern int exec_revoke(void);
+
+static uint8_t ready_to_revoke;
+
+void allow_revoke(void)
+{
+	ready_to_revoke = 1;
+}
+
+int revoke(void)
+{
+	if (ready_to_revoke) {
+		return exec_revoke();
+	}
+	return BOOT_KEY_REVOKE_NOT_READY;
+}
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index ddfe3793b8..abcc18fe38 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -76,6 +76,10 @@ int pcd_version_cmp_net(const struct flash_area *fap, struct image_header *hdr);
 
 #include "mcuboot_config/mcuboot_config.h"
 
+#if defined(CONFIG_BOOT_KEYS_REVOCATION)
+#include "bootutil/key_revocation.h"
+#endif
+
 BOOT_LOG_MODULE_DECLARE(mcuboot);
 
 static struct boot_loader_state boot_data;
@@ -2819,6 +2823,11 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
         }
     }
 
+#if defined(CONFIG_BOOT_KEYS_REVOCATION)
+    if (BOOT_SWAP_TYPE(state) == BOOT_SWAP_TYPE_NONE) {
+        allow_revoke();
+    }
+#endif
     /* Iterate over all the images. At this point all required update operations
      * have finished. By the end of the loop each image in the primary slot will
      * have been re-validated.
@@ -2924,6 +2933,12 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
     fill_rsp(state, rsp);
 
     fih_rc = FIH_SUCCESS;
+#if defined(CONFIG_BOOT_KEYS_REVOCATION)
+    rc = revoke();
+    if (rc != BOOT_KEY_REVOKE_OK) {
+        FIH_SET(fih_rc, FIH_FAILURE);
+    }
+#endif /* CONFIG_BOOT_KEYS_REVOCATION */
 out:
     /*
      * Since the boot_status struct stores plaintext encryption keys, reset
diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt
index 7b291a37b9..2673ac9eb4 100644
--- a/boot/zephyr/CMakeLists.txt
+++ b/boot/zephyr/CMakeLists.txt
@@ -99,6 +99,12 @@ if(DEFINED CONFIG_BOOT_SHARE_BACKEND_RETENTION)
     )
 endif()
 
+if(DEFINED CONFIG_BOOT_KEYS_REVOCATION)
+  zephyr_library_sources(
+    ${BOOT_DIR}/bootutil/src/key_revocation.c
+)
+endif()
+
 # Generic bootutil sources and includes.
 zephyr_library_include_directories(${BOOT_DIR}/bootutil/include)
 zephyr_library_sources(
diff --git a/boot/zephyr/Kconfig b/boot/zephyr/Kconfig
index 493af7e60f..c6eb7537f8 100644
--- a/boot/zephyr/Kconfig
+++ b/boot/zephyr/Kconfig
@@ -359,6 +359,18 @@ config BOOT_SIGNATURE_KMU_SLOTS
 
 endif
 
+config BOOT_KEYS_REVOCATION
+	bool "Auto revoke previous gen key"
+	help
+	  Automatically revoke previous generation key upon new valid key usage.
+
+config BOOT_KMU_KEYS_REVOCATION
+	bool
+	depends on BOOT_KEYS_REVOCATION
+	default y if BOOT_SIGNATURE_USING_KMU
+	help
+	  Enabling KMU key revocation backend.
+
 if !BOOT_SIGNATURE_USING_KMU
 
 config BOOT_SIGNATURE_KEY_FILE


Reviews:
user: login: nordicjm
body: Why can't this go upstream, these seem like normal psa functions, nothing nrfx specific?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T14:36:53Z
user: login: nvlsianpu
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T15:00:13Z
user: login: nordicjm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T09:36:58Z
user: login: nvlsianpu
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-02T10:50:03Z
user: login: nordicjm
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:41:01Z
user: login: de-nordic
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:48:36Z

Review comments:
diff_hunk: @@ -2838,6 +2847,12 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
     fill_rsp(state, rsp);
 
     fih_rc = FIH_SUCCESS;
+#if defined(CONFIG_BOOT_KEYS_REVOCATION)
+    rc = revoke();
+        if( rc != 0){
path: boot/bootutil/src/loader.c
user: login: nvlsianpu
body: indentation
created_at: 2025-04-01T14:38:25Z
author_association: COLLABORATOR
diff_hunk: @@ -114,6 +118,9 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
                                     EDDSA_SIGNAGURE_LENGTH);
         if (status == PSA_SUCCESS) {
             ret = 1;
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+            validated_with = kmu_key_ids + i;
path: boot/bootutil/src/ed25519_psa.c
user: login: nvlsianpu
body: `kid` value can be assigned -  Is psa_verify_message() callback modifying it?
created_at: 2025-04-01T14:51:38Z
author_association: COLLABORATOR
diff_hunk: @@ -122,4 +129,39 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
 
     return ret;
 }
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+int exec_revoke(void)
+{
+    int ret = 0;
+
+    if(!validated_with) {
path: boot/bootutil/src/ed25519_psa.c
user: login: nordicjm
body: space between if and bracket
created_at: 2025-04-02T09:34:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -122,4 +129,39 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
 
     return ret;
 }
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+int exec_revoke(void)
+{
+    int ret = 0;
+
+    if(!validated_with) {
+        ret = 1;
+        goto out;
+    }
+    psa_status_t status = psa_crypto_init();
path: boot/bootutil/src/ed25519_psa.c
user: login: nordicjm
body: declare variables at top of scope
created_at: 2025-04-02T09:34:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2025, Nordic Semiconductor ASA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <bootutil/key_revocation.h>
+
+extern void exec_revoke(void);
+
+static uint8_t ready_to_revoke;
+
+void allow_revoke(void)
+{
+	ready_to_revoke = 1;
+}
+
+int revoke(void)
+{
+	if(ready_to_revoke) {
path: boot/bootutil/src/key_revocation.c
user: login: nordicjm
body: here too
created_at: 2025-04-02T09:35:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -122,4 +129,39 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
 
     return ret;
 }
+#if defined(CONFIG_BOOT_KMU_KEYS_REVOCATION)
+int exec_revoke(void)
+{
+    int ret = 0;
+
+    if(!validated_with) {
+        ret = 1;
path: boot/bootutil/src/ed25519_psa.c
user: login: nordicjm
body: use defines for these error codes (apply to whole PR)
created_at: 2025-04-02T09:35:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2838,6 +2847,12 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
     fill_rsp(state, rsp);
 
     fih_rc = FIH_SUCCESS;
+#if defined(CONFIG_BOOT_KEYS_REVOCATION)
+    rc = revoke();
+    if( rc != 0){
path: boot/bootutil/src/loader.c
user: login: nordicjm
body: weird spacing
created_at: 2025-04-02T09:35:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,6 +91,12 @@ if(DEFINED CONFIG_BOOT_SHARE_BACKEND_RETENTION)
     )
 endif()
 
+if(DEFINED CONFIG_BOOT_KEYS_REVOCATION)
+zephyr_library_sources(
path: boot/zephyr/CMakeLists.txt
user: login: nordicjm
body: 2 space indent in cmake
created_at: 2025-04-02T09:35:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,6 +329,20 @@ config BOOT_SIGNATURE_KMU_SLOTS
 
 endif
 
+config BOOT_KEYS_REVOCATION
+	bool "Auto revoke previous gen key"
+	default n
path: boot/zephyr/Kconfig
user: login: nordicjm
body: ```suggestion
```
created_at: 2025-04-02T09:36:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,6 +329,20 @@ config BOOT_SIGNATURE_KMU_SLOTS
 
 endif
 
+config BOOT_KEYS_REVOCATION
+	bool "Auto revoke previous gen key"
+	default n
+	help
+	  Automatically revoke previous generation key upon new valid key usage.
+
+config BOOT_KMU_KEYS_REVOCATION
+	bool
+	depends on BOOT_KEYS_REVOCATION
+	default y if BOOT_SIGNATURE_USING_KMU
+	default n
path: boot/zephyr/Kconfig
user: login: nordicjm
body: ```suggestion
```
created_at: 2025-04-02T09:36:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: de-nordic
created_at: 2025-03-25T14:39:00Z
body: > Why can't this go upstream, these seem like normal psa functions, nothing nrfx specific?

I do not think we have already done upstream of KMU
author_association: CONTRIBUTOR
user: login: nvlsianpu
created_at: 2025-04-01T15:11:20Z
body: >> Why can't this go upstream, these seem like normal psa functions, nothing nrfx specific?

> I do not think we have already done upstream of KMU

We don't. Upstreaming this will mean to rework this to give some abstraction for proper `kid` identification and minimal key management API. @de-nordic Is working on that and more - result of his work will go to upstream along with relevant implementation of key-revocation scheme.
author_association: COLLABORATOR
user: login: nvlsianpu
created_at: 2025-04-02T10:50:50Z
body: @michalek-no Use Nordic 5 clauses license in new files.
author_association: COLLABORATOR
user: login: michalek-no
created_at: 2025-04-02T13:08:26Z
body: rebase
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-02T13:08:54Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=nrfconnect_sdk-mcuboot&pullRequest=404) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [D Reliability Rating on New Code](https://sonarcloud.io/dashboard?id=nrfconnect_sdk-mcuboot&pullRequest=404) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=nrfconnect_sdk-mcuboot&pullRequest=404)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE