feat(core): derive entropy from master key
What changed, and why it matters
This commit changes how some Trezor hardware wallets generate a secret internal 'entropy' value used to protect stored data. For newer devices (STM32U5-based models), the entropy is now derived from a master key already inside the secure chip, using a new 'fw_type' field in the firmware vendor header. For older devices, the previous method using CPU ID and one-time-programmable memory is kept. The change also updates the vendor header format to include a firmware-type byte. There is no direct evidence in the commit of a security vulnerability or fix; it appears to be a design/architecture change.
Treat as a normal architecture/security-hardening change. Reviewers should verify that secret_key_storage_salt derivation is cryptographically sound, that fw_type values cannot be manipulated by an attacker, and that the non-production fallback does not reach production builds. No immediate incident response is indicated by the diff alone.
Security signals we found
Entropy source changed from device-unique hardware values to a key-derived value on newer devices
New vendor_header.fw_type field influences key derivation (secret_key_storage_salt(vhdr.fw_type, ...))
Non-production builds allow failure of storage salt retrieval, falling back to zeroed entropy
Vendor header binary format changed (added fw_type byte, reserved padding reduced from 10 to 9 bytes)
No changelog entry and no explicit security explanation in commit message
Evidence from the diff
The patch refactors the entropy subsystem in Trezor firmware. It replaces the fixed 44-byte HW_ENTROPY_LEN buffer with a sized entropy_data_t structure (max 44 bytes). On STM32U5 devices with SECRET_PRIVILEGED_MASTER_KEY_SLOT, entropy_init() now derives entropy via secret_key_storage_salt(), keyed by vendor_header.fw_type read from FIRMWARE_START. On STM32F4 and STM32U5 without the privileged master key slot, legacy CPUID+OTP entropy is preserved. Vendor header JSON specs and binaries gain a fw_type field (1=custom, 2=universal, 3=BTC-only, 4=prodtest), and image parsing/building tools are updated accordingly. Syscall/supervisor-call verifiers now copy the full entropy_data_t struct.
Changed components
core/embed/sec/entropy (entropy initialization and retrieval)core/embed/sec/secret/stm32u5/secret_keys.c (new secret_key_storage_salt derivation)core/embed/util/image (vendor header parsing/format)core/embed/sys/syscall and sys/smcall (verified entropy_get dispatch)core/embed/rust/src/trezorhal/storage.rs (Rust storage initialization)core/embed/upymod/modtrezorconfig (MicroPython storage initialization)core/tools/build_vendorheader and python/src/trezorlib/firmware/vendor.py (vendor header tooling)D002 and T3W1 vendor header JSON/binariesInspect captured patch +174 / −61
diff --git a/core/embed/models/D002/vendorheader/vendor_dev_DO_NOT_SIGN.json b/core/embed/models/D002/vendorheader/vendor_dev_DO_NOT_SIGN.json
index c1c8b392..d389435c 100644
--- a/core/embed/models/D002/vendorheader/vendor_dev_DO_NOT_SIGN.json
+++ b/core/embed/models/D002/vendorheader/vendor_dev_DO_NOT_SIGN.json
@@ -2,6 +2,7 @@
"header_len": 4608,
"text": "DEV ONLY, DO NOT USE!",
"hw_model": "D002",
+ "fw_type": 2,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/D002/vendorheader/vendor_prodtest_DO_NOT_SIGN.json b/core/embed/models/D002/vendorheader/vendor_prodtest_DO_NOT_SIGN.json
index 1f81e046..2f7d72da 100644
--- a/core/embed/models/D002/vendorheader/vendor_prodtest_DO_NOT_SIGN.json
+++ b/core/embed/models/D002/vendorheader/vendor_prodtest_DO_NOT_SIGN.json
@@ -2,6 +2,7 @@
"header_len": 4608,
"text": "UNSAFE, DO NOT USE!",
"hw_model": "D002",
+ "fw_type": 4,
"expiry": 0,
"version": [0, 1],
"sig_m": 2,
diff --git a/core/embed/models/D002/vendorheader/vendor_unsafe.json b/core/embed/models/D002/vendorheader/vendor_unsafe.json
index 368ab5b0..c716388d 100644
--- a/core/embed/models/D002/vendorheader/vendor_unsafe.json
+++ b/core/embed/models/D002/vendorheader/vendor_unsafe.json
@@ -2,6 +2,7 @@
"header_len": 4608,
"text": "UNSAFE, DO NOT USE!",
"hw_model": "D002",
+ "fw_type": 1,
"expiry": 0,
"version": [0, 1],
"sig_m": 2,
diff --git a/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin b/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin
index 04ae5e11..a58b6113 100644
Binary files a/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin and b/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin differ
diff --git a/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin b/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin
index e6c4abcf..373d024c 100644
Binary files a/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin and b/core/embed/models/D002/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin differ
diff --git a/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin b/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin
index 12b034e4..df842c2f 100644
Binary files a/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin and b/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin differ
diff --git a/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin b/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin
index d2040130..8359bf5a 100644
Binary files a/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin and b/core/embed/models/D002/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin differ
diff --git a/core/embed/models/D002/vendorheader/vendorheader_unsafe_signed_dev.bin b/core/embed/models/D002/vendorheader/vendorheader_unsafe_signed_dev.bin
index 45689bba..40bcc544 100644
Binary files a/core/embed/models/D002/vendorheader/vendorheader_unsafe_signed_dev.bin and b/core/embed/models/D002/vendorheader/vendorheader_unsafe_signed_dev.bin differ
diff --git a/core/embed/models/D002/vendorheader/vendorheader_unsafe_unsigned.bin b/core/embed/models/D002/vendorheader/vendorheader_unsafe_unsigned.bin
index a73347c0..72b81fd1 100644
Binary files a/core/embed/models/D002/vendorheader/vendorheader_unsafe_unsigned.bin and b/core/embed/models/D002/vendorheader/vendorheader_unsafe_unsigned.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendor_dev_DO_NOT_SIGN.json b/core/embed/models/T3W1/vendorheader/vendor_dev_DO_NOT_SIGN.json
index d6dec11e..42e0944d 100644
--- a/core/embed/models/T3W1/vendorheader/vendor_dev_DO_NOT_SIGN.json
+++ b/core/embed/models/T3W1/vendorheader/vendor_dev_DO_NOT_SIGN.json
@@ -2,6 +2,7 @@
"header_len": 1024,
"text": "DEV ONLY, DO NOT USE!",
"hw_model": "T3W1",
+ "fw_type": 2,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/T3W1/vendorheader/vendor_prodtest.json b/core/embed/models/T3W1/vendorheader/vendor_prodtest.json
index 54b3b4fa..569ab99d 100644
--- a/core/embed/models/T3W1/vendorheader/vendor_prodtest.json
+++ b/core/embed/models/T3W1/vendorheader/vendor_prodtest.json
@@ -2,6 +2,7 @@
"header_len": 1024,
"text": "UNSAFE, FACTORY TEST ONLY",
"hw_model": "T3W1",
+ "fw_type": 4,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/T3W1/vendorheader/vendor_prodtest_DO_NOT_SIGN.json b/core/embed/models/T3W1/vendorheader/vendor_prodtest_DO_NOT_SIGN.json
index 3e52b668..5d5d41fb 100644
--- a/core/embed/models/T3W1/vendorheader/vendor_prodtest_DO_NOT_SIGN.json
+++ b/core/embed/models/T3W1/vendorheader/vendor_prodtest_DO_NOT_SIGN.json
@@ -2,6 +2,7 @@
"header_len": 1024,
"text": "UNSAFE, FACTORY TEST ONLY",
"hw_model": "T3W1",
+ "fw_type": 4,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/T3W1/vendorheader/vendor_trezor.json b/core/embed/models/T3W1/vendorheader/vendor_trezor.json
index 85b26419..6e15e13a 100644
--- a/core/embed/models/T3W1/vendorheader/vendor_trezor.json
+++ b/core/embed/models/T3W1/vendorheader/vendor_trezor.json
@@ -2,6 +2,7 @@
"header_len": 1024,
"text": "Trezor",
"hw_model": "T3W1",
+ "fw_type": 2,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/T3W1/vendorheader/vendor_trezor_btconly.json b/core/embed/models/T3W1/vendorheader/vendor_trezor_btconly.json
index 4f7f84c3..636e7ca3 100644
--- a/core/embed/models/T3W1/vendorheader/vendor_trezor_btconly.json
+++ b/core/embed/models/T3W1/vendorheader/vendor_trezor_btconly.json
@@ -2,6 +2,7 @@
"header_len": 1024,
"text": "Trezor Bitcoin-only",
"hw_model": "T3W1",
+ "fw_type": 3,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/T3W1/vendorheader/vendor_unsafe.json b/core/embed/models/T3W1/vendorheader/vendor_unsafe.json
index 1db42051..5c2cad4c 100644
--- a/core/embed/models/T3W1/vendorheader/vendor_unsafe.json
+++ b/core/embed/models/T3W1/vendorheader/vendor_unsafe.json
@@ -2,6 +2,7 @@
"header_len": 1024,
"text": "UNSAFE, DO NOT USE!",
"hw_model": "T3W1",
+ "fw_type": 1,
"expiry": 0,
"version": [0, 0],
"sig_m": 2,
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin b/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin
index f35b7d77..a4cc25ee 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_signed_dev.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin b/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin
index 940e1845..a828b68d 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_dev_DO_NOT_SIGN_unsigned.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin b/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin
index bba12bf4..c875e465 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_signed_dev.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin b/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin
index 8ec79c9d..ec2d50e2 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_DO_NOT_SIGN_unsigned.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_unsigned.bin b/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_unsigned.bin
index 4ce5418c..1bac7984 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_unsigned.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_prodtest_unsigned.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_trezor_btconly_unsigned.bin b/core/embed/models/T3W1/vendorheader/vendorheader_trezor_btconly_unsigned.bin
index 813def8b..107eb7e8 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_trezor_btconly_unsigned.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_trezor_btconly_unsigned.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_trezor_unsigned.bin b/core/embed/models/T3W1/vendorheader/vendorheader_trezor_unsigned.bin
index 63bfbba6..3636bddc 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_trezor_unsigned.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_trezor_unsigned.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_signed_dev.bin b/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_signed_dev.bin
index b3425343..1e59db5e 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_signed_dev.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_signed_dev.bin differ
diff --git a/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_unsigned.bin b/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_unsigned.bin
index 043768b2..e64141c7 100644
Binary files a/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_unsigned.bin and b/core/embed/models/T3W1/vendorheader/vendorheader_unsafe_unsigned.bin differ
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index b00f5e3e..431397ea 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -325,7 +325,8 @@ fn generate_trezorhal_bindings() {
.allowlist_var("MODEL_INTERNAL_NAME")
.allowlist_var("MODEL_FULL_NAME")
// entropy
- .allowlist_var("HW_ENTROPY_LEN")
+ .allowlist_var("ENTROPY_MAX_SIZE")
+ .allowlist_type("entropy_data_t")
.allowlist_function("entropy_get")
// secbool
.allowlist_type("secbool")
diff --git a/core/embed/rust/src/trezorhal/storage.rs b/core/embed/rust/src/trezorhal/storage.rs
index 69d186a5..da4ed0d6 100644
--- a/core/embed/rust/src/trezorhal/storage.rs
+++ b/core/embed/rust/src/trezorhal/storage.rs
@@ -97,13 +97,15 @@ pub type StorageResult<T> = Result<T, StorageError>;
/// This function must be called before any other storage function.
pub fn init() {
unsafe {
- let mut entropy_data: [u8; ffi::HW_ENTROPY_LEN as usize] =
- [0; ffi::HW_ENTROPY_LEN as usize];
- ffi::entropy_get(entropy_data.as_mut_ptr());
+ let mut entropy: ffi::entropy_data_t = ffi::entropy_data_t {
+ bytes: [0; ffi::ENTROPY_MAX_SIZE as usize],
+ size: 0,
+ };
+ ffi::entropy_get(&mut entropy);
ffi::storage_init(
Some(callback_wrapper),
- entropy_data.as_ptr(),
- entropy_data.len() as u16,
+ entropy.bytes.as_ptr(),
+ entropy.size as u16,
);
}
}
diff --git a/core/embed/sec/entropy/inc/sec/entropy.h b/core/embed/sec/entropy/inc/sec/entropy.h
index 290c2a11..2046a579 100644
--- a/core/embed/sec/entropy/inc/sec/entropy.h
+++ b/core/embed/sec/entropy/inc/sec/entropy.h
@@ -17,19 +17,36 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef TREZORHAL_ENTROPY_H
-#define TREZORHAL_ENTROPY_H
+#pragma once
#include <trezor_types.h>
-#ifdef KERNEL_MODE
+#ifdef SECURE_MODE
+/**
+ * Initializes the entropy module.
+ * If entropy has not yet been generated for the device, it is generated now.
+ */
void entropy_init(void);
-#endif
-
-#define HW_ENTROPY_LEN (12 + 32)
-
-void entropy_get(uint8_t *buf);
+#endif // SECURE_MODE
-#endif // KERNEL_MODE
+/**
+ * Maximum size of generated entropy (minimum is 32 bytes).
+ * Newer devices derive entropy from the master key - 32 bytes.
+ * Older devices derive entropy from CPUID and OTP - 32 + 12 bytes.
+ */
+#define ENTROPY_MAX_SIZE (32 + 12)
+
+typedef struct {
+ /** Number of valid bytes in the bytes array */
+ size_t size;
+ /** Generated entropy bytes */
+ uint8_t bytes[ENTROPY_MAX_SIZE];
+} entropy_data_t;
+
+/**
+ * Retrieves the generated entropy buffer.
+ * @param entropy structure filled with the generated data.
+ */
+void entropy_get(entropy_data_t* entropy);
diff --git a/core/embed/sec/entropy/stm32f4/entropy.c b/core/embed/sec/entropy/stm32f4/entropy.c
index 495a7802..53712729 100644
--- a/core/embed/sec/entropy/stm32f4/entropy.c
+++ b/core/embed/sec/entropy/stm32f4/entropy.c
@@ -27,38 +27,42 @@
#include "stm32f4xx_ll_utils.h"
-#ifdef KERNEL_MODE
+#ifdef SECURE_MODE
-static uint8_t g_hw_entropy[HW_ENTROPY_LEN];
+static entropy_data_t g_entropy = {0};
void entropy_init(void) {
mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_OTP);
+ entropy_data_t* ent = &g_entropy;
+
// collect entropy from UUID
uint32_t w = LL_GetUID_Word0();
- memcpy(g_hw_entropy, &w, 4);
+ memcpy(&ent->bytes[0], &w, 4);
w = LL_GetUID_Word1();
- memcpy(g_hw_entropy + 4, &w, 4);
+ memcpy(&ent->bytes[4], &w, 4);
w = LL_GetUID_Word2();
- memcpy(g_hw_entropy + 8, &w, 4);
+ memcpy(&ent->bytes[8], &w, 4);
mpu_restore(mpu_mode);
// set entropy in the OTP randomness block
if (secfalse == flash_otp_is_locked(FLASH_OTP_BLOCK_RANDOMNESS)) {
- uint8_t entropy[FLASH_OTP_BLOCK_SIZE];
- random_buffer(entropy, FLASH_OTP_BLOCK_SIZE);
- ensure(flash_otp_write(FLASH_OTP_BLOCK_RANDOMNESS, 0, entropy,
+ uint8_t rnd_bytes[FLASH_OTP_BLOCK_SIZE];
+ random_buffer(rnd_bytes, FLASH_OTP_BLOCK_SIZE);
+ ensure(flash_otp_write(FLASH_OTP_BLOCK_RANDOMNESS, 0, rnd_bytes,
FLASH_OTP_BLOCK_SIZE),
NULL);
ensure(flash_otp_lock(FLASH_OTP_BLOCK_RANDOMNESS), NULL);
}
// collect entropy from OTP randomness block
- ensure(flash_otp_read(FLASH_OTP_BLOCK_RANDOMNESS, 0, g_hw_entropy + 12,
+ ensure(flash_otp_read(FLASH_OTP_BLOCK_RANDOMNESS, 0, &ent->bytes[12],
FLASH_OTP_BLOCK_SIZE),
NULL);
+
+ ent->size = 12 + FLASH_OTP_BLOCK_SIZE;
}
-void entropy_get(uint8_t *buf) { memcpy(buf, g_hw_entropy, HW_ENTROPY_LEN); }
+void entropy_get(entropy_data_t* entropy) { *entropy = g_entropy; }
-#endif // KERNEL_MODE
+#endif // SECURE_MODE
diff --git a/core/embed/sec/entropy/stm32u5/entropy.c b/core/embed/sec/entropy/stm32u5/entropy.c
index bab17935..6d0b45a9 100644
--- a/core/embed/sec/entropy/stm32u5/entropy.c
+++ b/core/embed/sec/entropy/stm32u5/entropy.c
@@ -21,44 +21,77 @@
#include <trezor_rtl.h>
#include <sec/entropy.h>
+#include <sec/secret_keys.h>
#include <sys/mpu.h>
#include <util/flash_otp.h>
+#include <util/image.h>
#include "rand.h"
#include "stm32u5xx_ll_utils.h"
#ifdef SECURE_MODE
-static uint8_t g_hw_entropy[HW_ENTROPY_LEN];
+static entropy_data_t g_entropy = {0};
+#ifdef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+
+// Entropy derived from master key
+void entropy_init(void) {
+ entropy_data_t* ent = &g_entropy;
+
+ vendor_header vhdr = {0};
+ ensure(read_vendor_header((const uint8_t*)FIRMWARE_START, &vhdr), NULL);
+
+ _Static_assert(SECRET_KEY_STORAGE_SALT_SIZE <= sizeof(ent->bytes));
+ secbool retval = secret_key_storage_salt(vhdr.fw_type, ent->bytes);
+
+#if PRODUCTION
+ ensure(retval, "Failed to get storage salt");
+#else
+ // In non-production builds, we allow failure to retrieve the storage salt,
+ // so we don't need to set up the master key every time the flash is erased.
+ (void)retval;
+#endif
+
+ ent->size = SECRET_KEY_STORAGE_SALT_SIZE;
+}
+
+#else
+
+// Legacy entropy generated from CPUID & radnom data in OTP
void entropy_init(void) {
mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_OTP);
+ entropy_data_t* ent = &g_entropy;
+
// collect entropy from UUID
uint32_t w = LL_GetUID_Word0();
- memcpy(g_hw_entropy, &w, 4);
+ memcpy(&ent->bytes[0], &w, 4);
w = LL_GetUID_Word1();
- memcpy(g_hw_entropy + 4, &w, 4);
+ memcpy(&ent->bytes[4], &w, 4);
w = LL_GetUID_Word2();
- memcpy(g_hw_entropy + 8, &w, 4);
+ memcpy(&ent->bytes[8], &w, 4);
mpu_restore(mpu_mode);
// set entropy in the OTP randomness block
if (secfalse == flash_otp_is_locked(FLASH_OTP_BLOCK_RANDOMNESS)) {
- uint8_t entropy[FLASH_OTP_BLOCK_SIZE];
- random_buffer(entropy, FLASH_OTP_BLOCK_SIZE);
- ensure(flash_otp_write(FLASH_OTP_BLOCK_RANDOMNESS, 0, entropy,
+ uint8_t rnd_bytes[FLASH_OTP_BLOCK_SIZE];
+ random_buffer(rnd_bytes, FLASH_OTP_BLOCK_SIZE);
+ ensure(flash_otp_write(FLASH_OTP_BLOCK_RANDOMNESS, 0, rnd_bytes,
FLASH_OTP_BLOCK_SIZE),
NULL);
- // ensure(flash_otp_lock(FLASH_OTP_BLOCK_RANDOMNESS), NULL);
}
// collect entropy from OTP randomness block
- ensure(flash_otp_read(FLASH_OTP_BLOCK_RANDOMNESS, 0, g_hw_entropy + 12,
+ ensure(flash_otp_read(FLASH_OTP_BLOCK_RANDOMNESS, 0, &ent->bytes[12],
FLASH_OTP_BLOCK_SIZE),
NULL);
+
+ ent->size = 12 + FLASH_OTP_BLOCK_SIZE;
}
-void entropy_get(uint8_t *buf) { memcpy(buf, g_hw_entropy, HW_ENTROPY_LEN); }
+#endif
+
+void entropy_get(entropy_data_t* entropy) { *entropy = g_entropy; }
#endif // SECURE_MODE
diff --git a/core/embed/sec/entropy/unix/entropy.c b/core/embed/sec/entropy/unix/entropy.c
index 8f942c57..416cc231 100644
--- a/core/embed/sec/entropy/unix/entropy.c
+++ b/core/embed/sec/entropy/unix/entropy.c
@@ -17,12 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <trezor_model.h>
#include <trezor_rtl.h>
#include <sec/entropy.h>
-static uint8_t g_hw_entropy[HW_ENTROPY_LEN];
+static entropy_data_t g_entropy = {0};
-void entropy_init(void) { memset(g_hw_entropy, 0, HW_ENTROPY_LEN); }
+void entropy_init(void) {
+ entropy_data_t* ent = &g_entropy;
+#ifdef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+ ent->size = 32;
+#else
+ ent->size = 32 + 12; // Legacy
+#endif
+}
-void entropy_get(uint8_t *buf) { memcpy(buf, g_hw_entropy, HW_ENTROPY_LEN); }
+void entropy_get(entropy_data_t* entropy) { *entropy = g_entropy; }
diff --git a/core/embed/sec/secret/inc/sec/secret_keys.h b/core/embed/sec/secret/inc/sec/secret_keys.h
index 8f4c5073..59fbf82a 100644
--- a/core/embed/sec/secret/inc/sec/secret_keys.h
+++ b/core/embed/sec/secret/inc/sec/secret_keys.h
@@ -66,4 +66,9 @@ secbool secret_validate_nrf_pairing(const uint8_t *message, size_t msg_len,
#endif
+#define SECRET_KEY_STORAGE_SALT_SIZE 32
+
+secbool secret_key_storage_salt(uint16_t fw_type,
+ uint8_t dest[SECRET_KEY_STORAGE_SALT_SIZE]);
+
#endif // SECURE_MODE
diff --git a/core/embed/sec/secret/stm32u5/secret_keys.c b/core/embed/sec/secret/stm32u5/secret_keys.c
index b7b55504..a607fef7 100644
--- a/core/embed/sec/secret/stm32u5/secret_keys.c
+++ b/core/embed/sec/secret/stm32u5/secret_keys.c
@@ -40,6 +40,7 @@
#define KEY_INDEX_TROPIC_PAIRING_PRIVILEGED 4
#define KEY_INDEX_TROPIC_MASKING 5
#define KEY_INDEX_NRF_PAIRING 6
+#define KEY_INDEX_STORAGE_SALT 7
static secbool secret_key_derive_sym(uint8_t slot, uint16_t index,
uint16_t subindex,
@@ -206,6 +207,13 @@ cleanup:
#endif
+secbool secret_key_storage_salt(uint16_t fw_type,
+ uint8_t dest[SECRET_KEY_STORAGE_SALT_SIZE]) {
+ _Static_assert(SECRET_KEY_STORAGE_SALT_SIZE == SHA256_DIGEST_LENGTH);
+ return secret_key_derive_sym(SECRET_UNPRIVILEGED_MASTER_KEY_SLOT,
+ KEY_INDEX_STORAGE_SALT, fw_type, dest);
+}
+
#else // SECRET_PRIVILEGED_MASTER_KEY_SLOT
#ifdef USE_OPTIGA
diff --git a/core/embed/sys/smcall/stm32/smcall_dispatch.c b/core/embed/sys/smcall/stm32/smcall_dispatch.c
index ad276cf2..746393af 100644
--- a/core/embed/sys/smcall/stm32/smcall_dispatch.c
+++ b/core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -296,8 +296,8 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
} break;
case SMCALL_ENTROPY_GET: {
- uint8_t *buf = (uint8_t *)args[0];
- entropy_get__verified(buf);
+ entropy_data_t *entropy = (entropy_data_t *)args[0];
+ entropy_get__verified(entropy);
} break;
case SMCALL_RNG_GET: {
diff --git a/core/embed/sys/smcall/stm32/smcall_stubs.c b/core/embed/sys/smcall/stm32/smcall_stubs.c
index b128437a..58155357 100644
--- a/core/embed/sys/smcall/stm32/smcall_stubs.c
+++ b/core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -294,8 +294,10 @@ secbool storage_next_counter(const uint16_t key, uint32_t *count) {
// entropy.h
// =============================================================================
-void entropy_get(uint8_t *buf) {
- smcall_invoke1((uint32_t)buf, SMCALL_ENTROPY_GET);
+#include <sec/entropy.h>
+
+void entropy_get(entropy_data_t *entropy) {
+ smcall_invoke1((uint32_t)entropy, SMCALL_ENTROPY_GET);
}
// =============================================================================
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.c b/core/embed/sys/smcall/stm32/smcall_verifiers.c
index 97513d51..10bb12fc 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.c
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.c
@@ -370,12 +370,12 @@ access_violation:
// ---------------------------------------------------------------------
-void entropy_get__verified(uint8_t *buf) {
- if (!probe_write_access(buf, HW_ENTROPY_LEN)) {
+void entropy_get__verified(entropy_data_t *entropy) {
+ if (!probe_write_access(entropy, sizeof(*entropy))) {
goto access_violation;
}
- entropy_get(buf);
+ entropy_get(entropy);
return;
access_violation:
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.h b/core/embed/sys/smcall/stm32/smcall_verifiers.h
index 86e42190..6febef7e 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.h
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.h
@@ -101,7 +101,7 @@ secbool storage_next_counter__verified(const uint16_t key, uint32_t *count);
// ---------------------------------------------------------------------
#include <sec/entropy.h>
-void entropy_get__verified(uint8_t *buf);
+void entropy_get__verified(entropy_data_t *entropy);
// ---------------------------------------------------------------------
#include <util/fwutils.h>
diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c
index 7225306a..e39dae1e 100644
--- a/core/embed/sys/syscall/stm32/syscall_dispatch.c
+++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -652,8 +652,8 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
} break;
case SYSCALL_ENTROPY_GET: {
- uint8_t *buf = (uint8_t *)args[0];
- entropy_get__verified(buf);
+ entropy_data_t *entropy = (entropy_data_t *)args[0];
+ entropy_get__verified(entropy);
} break;
case SYSCALL_TRANSLATIONS_WRITE: {
diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c
index e421ac5c..e8b0dacc 100644
--- a/core/embed/sys/syscall/stm32/syscall_stubs.c
+++ b/core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -604,8 +604,10 @@ secbool storage_next_counter(const uint16_t key, uint32_t *count) {
// entropy.h
// =============================================================================
-void entropy_get(uint8_t *buf) {
- syscall_invoke1((uint32_t)buf, SYSCALL_ENTROPY_GET);
+#include <sec/entropy.h>
+
+void entropy_get(entropy_data_t *entropy) {
+ syscall_invoke1((uint32_t)entropy, SYSCALL_ENTROPY_GET);
}
// =============================================================================
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c
index ec912ba3..c47d7198 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.c
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -728,12 +728,12 @@ access_violation:
// ---------------------------------------------------------------------
-void entropy_get__verified(uint8_t *buf) {
- if (!probe_write_access(buf, HW_ENTROPY_LEN)) {
+void entropy_get__verified(entropy_data_t *entropy) {
+ if (!probe_write_access(entropy, sizeof(*entropy))) {
goto access_violation;
}
- entropy_get(buf);
+ entropy_get(entropy);
return;
access_violation:
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h
index d70493be..d02364cf 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.h
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h
@@ -186,7 +186,7 @@ const uint8_t *translations_read__verified(uint32_t *len, uint32_t offset);
// ---------------------------------------------------------------------
#include <sec/entropy.h>
-void entropy_get__verified(uint8_t *buf);
+void entropy_get__verified(entropy_data_t *entropy);
// ---------------------------------------------------------------------
#include <util/fwutils.h>
diff --git a/core/embed/upymod/modtrezorconfig/modtrezorconfig.c b/core/embed/upymod/modtrezorconfig/modtrezorconfig.c
index 48c00d9f..98d0f509 100644
--- a/core/embed/upymod/modtrezorconfig/modtrezorconfig.c
+++ b/core/embed/upymod/modtrezorconfig/modtrezorconfig.c
@@ -55,16 +55,16 @@ static secbool wrapped_ui_wait_callback(uint32_t wait, uint32_t progress,
/// called from this module!
/// """
STATIC mp_obj_t mod_trezorconfig_init(size_t n_args, const mp_obj_t *args) {
- uint8_t entropy_data[HW_ENTROPY_LEN];
- entropy_get(entropy_data);
+ entropy_data_t entropy;
+ entropy_get(&entropy);
if (n_args > 0) {
MP_STATE_VM(trezorconfig_ui_wait_callback) = args[0];
- storage_init(wrapped_ui_wait_callback, entropy_data, HW_ENTROPY_LEN);
+ storage_init(wrapped_ui_wait_callback, entropy.bytes, entropy.size);
} else {
- storage_init(NULL, entropy_data, HW_ENTROPY_LEN);
+ storage_init(NULL, entropy.bytes, entropy.size);
}
- memzero(entropy_data, sizeof(entropy_data));
+ memzero(&entropy, sizeof(entropy));
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_init_obj, 0, 1,
diff --git a/core/embed/util/image/image.c b/core/embed/util/image/image.c
index 392d7567..615f75ca 100644
--- a/core/embed/util/image/image.c
+++ b/core/embed/util/image/image.c
@@ -273,6 +273,7 @@ secbool __wur read_vendor_header(const uint8_t *const data,
memcpy(&vhdr->vsig_n, data + 15, 1);
memcpy(&vhdr->vtrust, data + 16, 2);
memcpy(&vhdr->hw_model, data + 18, 4);
+ memcpy(&vhdr->fw_type, data + 22, 1);
if (vhdr->vsig_n > MAX_VENDOR_PUBLIC_KEYS) {
return secfalse;
diff --git a/core/embed/util/image/inc/util/image.h b/core/embed/util/image/inc/util/image.h
index 26088066..5e10a03a 100644
--- a/core/embed/util/image/inc/util/image.h
+++ b/core/embed/util/image/inc/util/image.h
@@ -88,6 +88,22 @@ typedef struct {
#define VTRUST_ALLOW_PROVISIONING 0x200
#define VTRUST_ALLOW_UNLIMITED_RUN 0x400
+// Globally defined values for the `vendor_header.fw_type` field.
+// !!! Do not modify existing values. Only add new ones if needed.
+//
+typedef enum {
+ // Reserved value (may appear in legacy vendor headers)
+ VENDOR_FW_TYPE_RESERVED = 0,
+ // Custom (unsafe) firmware
+ VENDOR_FW_TYPE_CUSTOM = 1,
+ // Trezor Universal firmware
+ VENDOR_FW_TYPE_UNIVERSAL = 2,
+ // Trezor Bitcoin-only firmware
+ VENDOR_FW_TYPE_BTC_ONLY = 3,
+ // Factory tester firmware
+ VENDOR_FW_TYPE_PRODTEST = 4,
+} vendor_fw_type_t;
+
typedef struct {
uint32_t magic;
uint32_t hdrlen;
@@ -97,6 +113,7 @@ typedef struct {
uint8_t vsig_n;
uint16_t vtrust;
uint32_t hw_model;
+ uint8_t fw_type;
// uint8_t reserved[10];
const uint8_t *vpub[MAX_VENDOR_PUBLIC_KEYS];
uint8_t vstr_len;
diff --git a/core/tools/build_vendorheader b/core/tools/build_vendorheader
index 87177756..a75540a1 100755
--- a/core/tools/build_vendorheader
+++ b/core/tools/build_vendorheader
@@ -62,6 +62,8 @@ def build_vendorheader(
spec["hw_model"] = b"\x00\x00\x00\x00"
else:
spec["hw_model"] = spec["hw_model"].encode("ascii")
+ if not "fw_type" in spec.keys():
+ spec["fw_type"] = 0
min_length = minimum_header_len(spec, quiet)
if "header_len" not in spec:
diff --git a/python/src/trezorlib/firmware/vendor.py b/python/src/trezorlib/firmware/vendor.py
index ebaf5f49..f96be5e1 100644
--- a/python/src/trezorlib/firmware/vendor.py
+++ b/python/src/trezorlib/firmware/vendor.py
@@ -95,6 +95,7 @@ class VendorHeader(Struct):
sig_m: int
# sig_n: int
hw_model: Model | bytes
+ fw_type: int
pubkeys: list[bytes]
text: str
image: dict[str, t.Any]
@@ -114,7 +115,8 @@ class VendorHeader(Struct):
"sig_n" / c.Rebuild(c.Int8ul, c.len_(c.this.pubkeys)),
"trust" / VendorTrust.SUBCON,
"hw_model" / EnumAdapter(c.Bytes(4), Model),
- "_reserved" / c.Padding(10),
+ "fw_type" / c.Int8ul,
+ "_reserved" / c.Padding(9),
"pubkeys" / c.Bytes(32)[c.this.sig_n],
"text" / c.Aligned(4, c.PascalString(c.Int8ul, "utf-8")),
"image" / ToifStruct,
Why this scored 32/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.