feat(core): Add MCU device attestation with ML-DSA-44.
What changed, and why it matters
This commit adds a new hardware-based device authentication feature for the Trezor T3W1 model. It lets the device prove its identity using a cryptographic certificate and signing key stored in the microcontroller (MCU). The change is a feature addition, not a fix for a known vulnerability. There is no evidence in the commit or supplied references that this resolves an active security issue or introduces a new attack path.
Treat as a normal feature commit. Review the secure-world implementation for side-channel resistance, deterministic signature behavior, and correct key derivation. Verify that the certificate chain parser in authenticate_device.py handles untrusted certificate data robustly. No urgent security action is indicated by the commit itself.
Security signals we found
New secure-world signing primitive added behind USE_MCU_ATTESTATION compile flag
Attestation seed derived from SECRET_PRIVILEGED_MASTER_KEY_SLOT via secret_key_derive_sym
SMCALL/SYSCALL verifiers check read/write access before passing pointers to secure world
Private key material and RNG seed are memzero'd after use in mcu_attestation_sign
Feature is gated to T3W1 model in build tooling
No vendor disclosure of security relevance, CVE, or researcher attribution present in commit
Evidence from the diff
The patch implements MCU device attestation using the ML-DSA-44 post-quantum signature scheme. It adds a secure-world driver (mcu_attestation.c) that derives an attestation seed from a privileged master key slot, generates an ephemeral ML-DSA keypair, and signs caller-supplied challenges. The certificate is passed from bootloader to firmware via startup_args. New SMCALL and SYSCALL gates expose cert_size, cert_read, and sign operations to less-privileged worlds, with memory-access verifiers. A MicroPython module (trezorcrypto.mcu) and a Python app (authenticate_device.py) wire the feature into the existing AuthenticateDevice/AuthenticityProof flow. The protobuf buffer is enlarged from 8192 to 8704 bytes to accommodate the new proof fields.
Changed components
core/embed/sec/mcu_attestationcore/embed/sec/secret_keyscore/embed/sys/smcall/stm32core/embed/sys/syscall/stm32core/embed/upymod/modtrezorcryptocore/src/apps/management/authenticate_device.pycore/src/trezor/crypto/__init__.pycore/src/trezor/wire/thp/memory_manager.pyInspect captured patch +582 / −4
diff --git a/core/.changelog.d/6807.added b/core/.changelog.d/6807.added
new file mode 100644
index 00000000..e4bd45d0
--- /dev/null
+++ b/core/.changelog.d/6807.added
@@ -0,0 +1 @@
+[T3W1] Added MCU device attestation with ML-DSA-44.
diff --git a/core/embed/projects/bootloader/.changelog.d/6807.added b/core/embed/projects/bootloader/.changelog.d/6807.added
new file mode 100644
index 00000000..151c9126
--- /dev/null
+++ b/core/embed/projects/bootloader/.changelog.d/6807.added
@@ -0,0 +1 @@
+[T3W1] Add the MCU device certificate to startup_args.
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index 7bc24bc8..9404cbbd 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -89,6 +89,9 @@
#ifdef USE_TRUSTZONE
#include <sec/tz_init.h>
#endif
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+#endif
#ifdef USE_BLE
#include "wire/wire_iface_ble.h"
@@ -441,6 +444,19 @@ void real_jump_to_firmware(void) {
ensure_secmon_min_version(secmon_hdr->monotonic);
#endif
+#ifdef USE_MCU_ATTESTATION
+ {
+ uint8_t mcu_device_cert[MCU_ATTESTATION_MAX_CERT_SIZE];
+ size_t mcu_device_cert_size = 0;
+ if (sectrue == secret_mcu_device_cert_read(mcu_device_cert,
+ sizeof(mcu_device_cert),
+ &mcu_device_cert_size)) {
+ startup_args_add(STARTUP_ARGS_TYPE_MCU_DEVICE_CERT, mcu_device_cert,
+ mcu_device_cert_size);
+ }
+ }
+#endif
+
#ifdef USE_SECRET
secbool provisioning_access =
((vhdr.vtrust & (VTRUST_ALLOW_PROVISIONING | VTRUST_SECRET_MASK)) ==
diff --git a/core/embed/projects/unix/main_main.c b/core/embed/projects/unix/main_main.c
index 3d1c96c8..54acdc3c 100644
--- a/core/embed/projects/unix/main_main.c
+++ b/core/embed/projects/unix/main_main.c
@@ -29,6 +29,7 @@
#include <sys/coreapp.h>
#include <sys/flash.h>
#include <sys/flash_otp.h>
+#include <sys/startup_args.h>
#include <sys/system.h>
#include <sys/systick.h>
#include <sys/systimer.h>
@@ -66,6 +67,10 @@
#include <sec/secret.h>
#endif
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+#endif
+
#include <SDL.h>
static void drivers_deinit(void) { flash_deinit(); }
@@ -168,6 +173,19 @@ static void kernel_loop(applet_t *coreapp) {
int main(int argc, char **argv) {
system_init(&rsod_panic_handler);
+#ifdef USE_MCU_ATTESTATION
+ {
+ uint8_t mcu_device_cert[MCU_ATTESTATION_MAX_CERT_SIZE];
+ size_t mcu_device_cert_size = 0;
+ if (sectrue == secret_mcu_device_cert_read(mcu_device_cert,
+ sizeof(mcu_device_cert),
+ &mcu_device_cert_size)) {
+ startup_args_add(STARTUP_ARGS_TYPE_MCU_DEVICE_CERT, mcu_device_cert,
+ mcu_device_cert_size);
+ }
+ }
+#endif
+
#if defined(USE_SECRET) && defined(LOCKABLE_BOOTLOADER)
secret_lock_bootloader();
#endif
@@ -176,6 +194,9 @@ int main(int argc, char **argv) {
ensure(sectrue * (zkp_context_init() == 0), NULL);
#endif
+ // Simulate the bootloader passing startup_args to firmware.
+ startup_args_import(startup_args_export());
+
drivers_init();
applet_t coreapp;
diff --git a/core/embed/sec/mcu_attestation/inc/sec/mcu_attestation.h b/core/embed/sec/mcu_attestation/inc/sec/mcu_attestation.h
new file mode 100644
index 00000000..3b94c728
--- /dev/null
+++ b/core/embed/sec/mcu_attestation/inc/sec/mcu_attestation.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program 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.
+ *
+ * This program 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#ifdef USE_MCU_ATTESTATION
+
+#include <mldsa_native.h>
+#include <trezor_model.h>
+#include <trezor_types.h>
+
+#define MCU_ATTESTATION_SIG_SIZE MLDSA_BYTES(MLD_CONFIG_API_PARAMETER_SET)
+#define MCU_ATTESTATION_PUBKEY_SIZE \
+ MLDSA_PUBLICKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)
+#define MCU_ATTESTATION_PRIVKEY_SIZE \
+ MLDSA_SECRETKEYBYTES(MLD_CONFIG_API_PARAMETER_SET)
+#define MCU_ATTESTATION_MAX_CERT_SIZE (SECRET_MCU_DEVICE_CERT_SIZE - 2)
+
+/**
+ * Get the size of the MCU device authentication certificate.
+ *
+ * @param cert_size Output parameter for certificate size.
+ * @return sectrue on success, secfalse on failure.
+ */
+secbool mcu_attestation_cert_size(size_t *cert_size);
+
+/**
+ * Get the MCU device authentication certificate.
+ *
+ * @param cert Buffer to store the certificate.
+ * @param max_cert_size Size of the certificate buffer.
+ * @param cert_size Output parameter for actual certificate size.
+ * @return sectrue on success, secfalse on failure.
+ */
+secbool mcu_attestation_cert_read(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size);
+
+/**
+ * Sign a challenge with the MCU device authentication key (ML-DSA-44).
+ *
+ * @param challenge Challenge bytes to sign.
+ * @param challenge_size Size of the challenge.
+ * @param signature Output parameter for the signature (MCU_ATTESTATION_SIG_SIZE
+ * bytes).
+ * @return sectrue on success, secfalse on failure.
+ */
+secbool mcu_attestation_sign(const uint8_t *challenge, size_t challenge_size,
+ uint8_t signature[MCU_ATTESTATION_SIG_SIZE]);
+
+#endif // USE_MCU_ATTESTATION
diff --git a/core/embed/sec/mcu_attestation/mcu_attestation.c b/core/embed/sec/mcu_attestation/mcu_attestation.c
new file mode 100644
index 00000000..8cf91d01
--- /dev/null
+++ b/core/embed/sec/mcu_attestation/mcu_attestation.c
@@ -0,0 +1,100 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program 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.
+ *
+ * This program 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef SECURE_MODE
+#ifdef USE_MCU_ATTESTATION
+
+#include <trezor_rtl.h>
+#include <trezor_types.h>
+
+#include <sec/mcu_attestation.h>
+#include <sec/secret_keys.h>
+#include <sys/rng.h>
+#include <sys/startup_args.h>
+
+#include <mldsa_native.h>
+
+#include "memzero.h"
+
+#ifndef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+#error "USE_MCU_ATTESTATION requires SECRET_PRIVILEGED_MASTER_KEY_SLOT."
+#endif
+
+secbool mcu_attestation_cert_size(size_t *cert_size) {
+ if (ts_ok(startup_args_get(STARTUP_ARGS_TYPE_MCU_DEVICE_CERT, NULL,
+ cert_size))) {
+ return sectrue;
+ }
+ return secfalse;
+}
+
+secbool mcu_attestation_cert_read(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size) {
+ const void *value = NULL;
+ size_t size = 0;
+ if (ts_error(
+ startup_args_get(STARTUP_ARGS_TYPE_MCU_DEVICE_CERT, &value, &size))) {
+ return secfalse;
+ }
+ if (size > max_cert_size) {
+ return secfalse;
+ }
+ memcpy(cert, value, size);
+ *cert_size = size;
+ return sectrue;
+}
+
+secbool mcu_attestation_sign(const uint8_t *challenge, size_t challenge_size,
+ uint8_t signature[MCU_ATTESTATION_SIG_SIZE]) {
+ secbool ret = secfalse;
+
+ uint8_t seed[MLDSA_SEEDBYTES] = {0};
+ if (secret_key_mcu_device_auth(seed) != sectrue) {
+ goto cleanup;
+ }
+
+ uint8_t mcu_public[MCU_ATTESTATION_PUBKEY_SIZE] = {0};
+ uint8_t mcu_private[MCU_ATTESTATION_PRIVKEY_SIZE] = {0};
+ if (mldsa_keypair_internal(mcu_public, mcu_private, seed) != 0) {
+ goto cleanup;
+ }
+
+ uint8_t rnd[MLDSA_RNDBYTES] = {0};
+ rng_fill_buffer(rnd, sizeof(rnd));
+
+ const uint8_t ENCODED_EMPTY_CONTEXT_STRING[] = {0, 0};
+ size_t siglen = 0;
+ if (mldsa_signature_internal(signature, &siglen, challenge, challenge_size,
+ ENCODED_EMPTY_CONTEXT_STRING,
+ sizeof(ENCODED_EMPTY_CONTEXT_STRING), rnd,
+ mcu_private, 0) != 0) {
+ goto cleanup;
+ }
+
+ ret = sectrue;
+
+cleanup:
+ memzero(seed, sizeof(seed));
+ memzero(mcu_private, sizeof(mcu_private));
+ memzero(rnd, sizeof(rnd));
+ return ret;
+}
+
+#endif // USE_MCU_ATTESTATION
+#endif // SECURE_MODE
diff --git a/core/embed/sec/secret_keys/inc/sec/secret_keys.h b/core/embed/sec/secret_keys/inc/sec/secret_keys.h
index 75008d34..d32f61e2 100644
--- a/core/embed/sec/secret_keys/inc/sec/secret_keys.h
+++ b/core/embed/sec/secret_keys/inc/sec/secret_keys.h
@@ -32,11 +32,15 @@ secbool secret_key_delegated_identity(uint16_t rotation_index,
#define SECRET_KEY_MASKING
+#endif // SECRET_MASTER_KEY_SLOT_SIZE
+
+#ifdef USE_MCU_ATTESTATION
+
#include <mldsa_native.h>
secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]);
-#endif // SECRET_MASTER_KEY_SLOT_SIZE
+#endif // USE_MCU_ATTESTATION
#ifdef USE_OPTIGA
diff --git a/core/embed/sec/secret_keys/stm32u5/secret_keys.c b/core/embed/sec/secret_keys/stm32u5/secret_keys.c
index 90dcf1af..54b3f63d 100644
--- a/core/embed/sec/secret_keys/stm32u5/secret_keys.c
+++ b/core/embed/sec/secret_keys/stm32u5/secret_keys.c
@@ -36,11 +36,13 @@
#ifdef SECRET_PRIVILEGED_MASTER_KEY_SLOT
+#ifdef USE_MCU_ATTESTATION
secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]) {
_Static_assert(MLDSA_SEEDBYTES == SHA256_DIGEST_LENGTH);
return secret_key_derive_sym(SECRET_PRIVILEGED_MASTER_KEY_SLOT,
KEY_INDEX_MCU_DEVICE_AUTH, 0, 0, dest);
}
+#endif // USE_MCU_ATTESTATION
#ifdef USE_OPTIGA
secbool secret_key_optiga_pairing(uint8_t dest[OPTIGA_PAIRING_SECRET_SIZE]) {
diff --git a/core/embed/sys/smcall/stm32/smcall_dispatch.c b/core/embed/sys/smcall/stm32/smcall_dispatch.c
index 5614b806..3f28afd7 100644
--- a/core/embed/sys/smcall/stm32/smcall_dispatch.c
+++ b/core/embed/sys/smcall/stm32/smcall_dispatch.c
@@ -228,6 +228,29 @@ __attribute((no_stack_protector)) void smcall_handler(uint32_t *args,
} break;
#endif
+#ifdef USE_MCU_ATTESTATION
+ case SMCALL_MCU_ATTESTATION_CERT_SIZE: {
+ size_t *cert_size = (size_t *)args[0];
+ args[0] = mcu_attestation_cert_size__verified(cert_size);
+ } break;
+
+ case SMCALL_MCU_ATTESTATION_CERT_READ: {
+ uint8_t *cert = (uint8_t *)args[0];
+ size_t max_cert_size = (size_t)args[1];
+ size_t *cert_size = (size_t *)args[2];
+ args[0] =
+ mcu_attestation_cert_read__verified(cert, max_cert_size, cert_size);
+ } break;
+
+ case SMCALL_MCU_ATTESTATION_SIGN: {
+ const uint8_t *challenge = (const uint8_t *)args[0];
+ size_t challenge_size = (size_t)args[1];
+ uint8_t *signature = (uint8_t *)args[2];
+ args[0] =
+ mcu_attestation_sign__verified(challenge, challenge_size, signature);
+ } break;
+#endif // USE_MCU_ATTESTATION
+
case SMCALL_STORAGE_SETUP: {
PIN_UI_WAIT_CALLBACK callback = (PIN_UI_WAIT_CALLBACK)args[0];
storage_setup__verified(callback);
diff --git a/core/embed/sys/smcall/stm32/smcall_numbers.h b/core/embed/sys/smcall/stm32/smcall_numbers.h
index 3f300571..f7197180 100644
--- a/core/embed/sys/smcall/stm32/smcall_numbers.h
+++ b/core/embed/sys/smcall/stm32/smcall_numbers.h
@@ -103,6 +103,9 @@ typedef enum {
SMCALL_BACKUP_RAM_WRITE,
SMCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY,
+ SMCALL_MCU_ATTESTATION_CERT_SIZE,
+ SMCALL_MCU_ATTESTATION_CERT_READ,
+ SMCALL_MCU_ATTESTATION_SIGN,
SMCALL_TELEMETRY_UPDATE_BATT_TEMP,
SMCALL_TELEMETRY_UPDATE_BATT_ERRORS,
diff --git a/core/embed/sys/smcall/stm32/smcall_stubs.c b/core/embed/sys/smcall/stm32/smcall_stubs.c
index cfc63be7..31bc6dd5 100644
--- a/core/embed/sys/smcall/stm32/smcall_stubs.c
+++ b/core/embed/sys/smcall/stm32/smcall_stubs.c
@@ -223,6 +223,30 @@ secbool secret_key_delegated_identity(uint16_t rotation_index,
#endif
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+
+secbool mcu_attestation_cert_size(size_t *cert_size) {
+ return (secbool)smcall_invoke1((uint32_t)cert_size,
+ SMCALL_MCU_ATTESTATION_CERT_SIZE);
+}
+
+secbool mcu_attestation_cert_read(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size) {
+ return (secbool)smcall_invoke3((uint32_t)cert, (uint32_t)max_cert_size,
+ (uint32_t)cert_size,
+ SMCALL_MCU_ATTESTATION_CERT_READ);
+}
+
+secbool mcu_attestation_sign(const uint8_t *challenge, size_t challenge_size,
+ uint8_t signature[MCU_ATTESTATION_SIG_SIZE]) {
+ return (secbool)smcall_invoke3((uint32_t)challenge, (uint32_t)challenge_size,
+ (uint32_t)signature,
+ SMCALL_MCU_ATTESTATION_SIGN);
+}
+
+#endif // USE_MCU_ATTESTATION
+
// =============================================================================
// storage.h
// =============================================================================
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.c b/core/embed/sys/smcall/stm32/smcall_verifiers.c
index 9fa39bdf..40eda49d 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.c
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.c
@@ -235,6 +235,57 @@ access_violation:
}
#endif
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+
+secbool mcu_attestation_cert_size__verified(size_t *cert_size) {
+ if (!probe_write_access(cert_size, sizeof(*cert_size))) {
+ goto access_violation;
+ }
+
+ return mcu_attestation_cert_size(cert_size);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+
+secbool mcu_attestation_cert_read__verified(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size) {
+ if (!probe_write_access(cert, max_cert_size)) {
+ goto access_violation;
+ }
+
+ if (!probe_write_access(cert_size, sizeof(*cert_size))) {
+ goto access_violation;
+ }
+
+ return mcu_attestation_cert_read(cert, max_cert_size, cert_size);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+
+secbool mcu_attestation_sign__verified(const uint8_t *challenge,
+ size_t challenge_size,
+ uint8_t *signature) {
+ if (!probe_read_access(challenge, challenge_size)) {
+ goto access_violation;
+ }
+
+ if (!probe_write_access(signature, MCU_ATTESTATION_SIG_SIZE)) {
+ goto access_violation;
+ }
+
+ return mcu_attestation_sign(challenge, challenge_size, signature);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+#endif // USE_MCU_ATTESTATION
+
// ---------------------------------------------------------------------
typedef __attribute__((
diff --git a/core/embed/sys/smcall/stm32/smcall_verifiers.h b/core/embed/sys/smcall/stm32/smcall_verifiers.h
index eac8bb8d..f3b52ca0 100644
--- a/core/embed/sys/smcall/stm32/smcall_verifiers.h
+++ b/core/embed/sys/smcall/stm32/smcall_verifiers.h
@@ -80,6 +80,18 @@ secbool secret_key_delegated_identity__verified(
uint16_t rotation_index, uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
#endif
+
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+secbool mcu_attestation_cert_size__verified(size_t *cert_size);
+
+secbool mcu_attestation_cert_read__verified(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size);
+
+secbool mcu_attestation_sign__verified(const uint8_t *challenge,
+ size_t challenge_size,
+ uint8_t *signature);
+#endif
// ---------------------------------------------------------------------
#include <sec/storage.h>
diff --git a/core/embed/sys/startup/inc/sys/startup_args.h b/core/embed/sys/startup/inc/sys/startup_args.h
index 5cdf717a..eac79de0 100644
--- a/core/embed/sys/startup/inc/sys/startup_args.h
+++ b/core/embed/sys/startup/inc/sys/startup_args.h
@@ -43,6 +43,8 @@ typedef struct {
typedef enum {
/** Invalid argument type */
STARTUP_ARGS_TYPE_INVALID = 0,
+ /** MCU device attestation certificate */
+ STARTUP_ARGS_TYPE_MCU_DEVICE_CERT = 1,
} startup_args_type_t;
diff --git a/core/embed/sys/syscall/inc/sys/syscall_numbers.h b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
index a16553c7..893b2748 100644
--- a/core/embed/sys/syscall/inc/sys/syscall_numbers.h
+++ b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
@@ -199,6 +199,9 @@ typedef enum {
SYSCALL_STORAGE_GET,
SYSCALL_SECRET_KEYS_GET_DELEGATED_IDENTITY_KEY,
+ SYSCALL_MCU_ATTESTATION_CERT_SIZE,
+ SYSCALL_MCU_ATTESTATION_CERT_READ,
+ SYSCALL_MCU_ATTESTATION_SIGN,
SYSCALL_TELEMETRY_GET,
// ------------------------------------------------------
diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c
index 6fcb289f..c4740d31 100644
--- a/core/embed/sys/syscall/stm32/syscall_dispatch.c
+++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -507,6 +507,29 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
} break;
#endif
+#ifdef USE_MCU_ATTESTATION
+ case SYSCALL_MCU_ATTESTATION_CERT_SIZE: {
+ size_t *cert_size = (size_t *)args[0];
+ args[0] = mcu_attestation_cert_size__verified(cert_size);
+ } break;
+
+ case SYSCALL_MCU_ATTESTATION_CERT_READ: {
+ uint8_t *cert = (uint8_t *)args[0];
+ size_t max_cert_size = (size_t)args[1];
+ size_t *cert_size = (size_t *)args[2];
+ args[0] =
+ mcu_attestation_cert_read__verified(cert, max_cert_size, cert_size);
+ } break;
+
+ case SYSCALL_MCU_ATTESTATION_SIGN: {
+ const uint8_t *challenge = (const uint8_t *)args[0];
+ size_t challenge_size = (size_t)args[1];
+ uint8_t *signature = (uint8_t *)args[2];
+ args[0] =
+ mcu_attestation_sign__verified(challenge, challenge_size, signature);
+ } break;
+#endif // USE_MCU_ATTESTATION
+
#ifdef USE_TELEMETRY
case SYSCALL_TELEMETRY_GET: {
telemetry_data_t *out = (telemetry_data_t *)args[0];
diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c
index 83d6d3a3..da127d1b 100644
--- a/core/embed/sys/syscall/stm32/syscall_stubs.c
+++ b/core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -349,6 +349,30 @@ secbool secret_bootloader_locked(void) {
#endif
// =============================================================================
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+
+secbool mcu_attestation_cert_size(size_t *cert_size) {
+ return (secbool)syscall_invoke1((uint32_t)cert_size,
+ SYSCALL_MCU_ATTESTATION_CERT_SIZE);
+}
+
+secbool mcu_attestation_cert_read(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size) {
+ return (secbool)syscall_invoke3((uint32_t)cert, (uint32_t)max_cert_size,
+ (uint32_t)cert_size,
+ SYSCALL_MCU_ATTESTATION_CERT_READ);
+}
+
+secbool mcu_attestation_sign(const uint8_t *challenge, size_t challenge_size,
+ uint8_t signature[MCU_ATTESTATION_SIG_SIZE]) {
+ return (secbool)syscall_invoke3((uint32_t)challenge, (uint32_t)challenge_size,
+ (uint32_t)signature,
+ SYSCALL_MCU_ATTESTATION_SIGN);
+}
+
+#endif // USE_MCU_ATTESTATION
+
// button.h
// =============================================================================
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c
index 3409c6aa..e9524dd6 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.c
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -599,6 +599,57 @@ access_violation:
}
#endif
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+
+secbool mcu_attestation_cert_size__verified(size_t *cert_size) {
+ if (!probe_write_access(cert_size, sizeof(*cert_size))) {
+ goto access_violation;
+ }
+
+ return mcu_attestation_cert_size(cert_size);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+
+secbool mcu_attestation_cert_read__verified(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size) {
+ if (!probe_write_access(cert, max_cert_size)) {
+ goto access_violation;
+ }
+
+ if (!probe_write_access(cert_size, sizeof(*cert_size))) {
+ goto access_violation;
+ }
+
+ return mcu_attestation_cert_read(cert, max_cert_size, cert_size);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+
+secbool mcu_attestation_sign__verified(const uint8_t *challenge,
+ size_t challenge_size,
+ uint8_t *signature) {
+ if (!probe_read_access(challenge, challenge_size)) {
+ goto access_violation;
+ }
+
+ if (!probe_write_access(signature, MCU_ATTESTATION_SIG_SIZE)) {
+ goto access_violation;
+ }
+
+ return mcu_attestation_sign(challenge, challenge_size, signature);
+
+access_violation:
+ apptask_access_violation();
+ return secfalse;
+}
+#endif // USE_MCU_ATTESTATION
+
// ---------------------------------------------------------------------
#ifdef USE_TELEMETRY
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h
index 53a9b8f4..f685a9bf 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.h
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h
@@ -169,6 +169,18 @@ secbool secret_key_delegated_identity__verified(
uint16_t index, uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]);
#endif
+#ifdef USE_MCU_ATTESTATION
+#include <sec/mcu_attestation.h>
+secbool mcu_attestation_cert_size__verified(size_t *cert_size);
+
+secbool mcu_attestation_cert_read__verified(uint8_t *cert, size_t max_cert_size,
+ size_t *cert_size);
+
+secbool mcu_attestation_sign__verified(const uint8_t *challenge,
+ size_t challenge_size,
+ uint8_t *signature);
+#endif
+
// ---------------------------------------------------------------------
#ifdef USE_TELEMETRY
diff --git a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-mcu.h b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-mcu.h
new file mode 100644
index 00000000..9cf636bb
--- /dev/null
+++ b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-mcu.h
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program 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.
+ *
+ * This program 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef USE_MCU_ATTESTATION
+
+#include <sec/mcu_attestation.h>
+
+/// package: trezorcrypto.mcu
+
+/// def get_certificate() -> bytes:
+/// """
+/// Return MCU device certificate.
+/// """
+STATIC mp_obj_t mod_trezorcrypto_mcu_get_certificate(void) {
+ size_t cert_size = 0;
+ if (mcu_attestation_cert_size(&cert_size) != sectrue) {
+ mp_raise_msg(&mp_type_RuntimeError,
+ MP_ERROR_TEXT("Failed to get certificate size."));
+ }
+
+ vstr_t cert = {0};
+ vstr_init_len(&cert, cert_size);
+ if (mcu_attestation_cert_read((uint8_t *)cert.buf, cert.alloc, &cert_size) !=
+ sectrue) {
+ vstr_clear(&cert);
+ mp_raise_msg(&mp_type_RuntimeError,
+ MP_ERROR_TEXT("Failed to read certificate."));
+ }
+
+ cert.len = cert_size;
+ return mp_obj_new_str_from_vstr(&mp_type_bytes, &cert);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorcrypto_mcu_get_certificate_obj,
+ mod_trezorcrypto_mcu_get_certificate);
+
+/// def sign(challenge: AnyBytes) -> bytes:
+/// """
+/// Sign challenge bytes with MCU device attestation key.
+/// """
+STATIC mp_obj_t mod_trezorcrypto_mcu_sign(mp_obj_t challenge) {
+ mp_buffer_info_t challenge_buf = {0};
+ mp_get_buffer_raise(challenge, &challenge_buf, MP_BUFFER_READ);
+
+ vstr_t sig = {0};
+ vstr_init_len(&sig, MCU_ATTESTATION_SIG_SIZE);
+ if (mcu_attestation_sign((const uint8_t *)challenge_buf.buf,
+ challenge_buf.len, (uint8_t *)sig.buf) != sectrue) {
+ vstr_clear(&sig);
+ mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Signing failed."));
+ }
+
+ sig.len = MCU_ATTESTATION_SIG_SIZE;
+ return mp_obj_new_str_from_vstr(&mp_type_bytes, &sig);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_mcu_sign_obj,
+ mod_trezorcrypto_mcu_sign);
+
+STATIC const mp_rom_map_elem_t mod_trezorcrypto_mcu_globals_table[] = {
+ {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mcu)},
+ {MP_ROM_QSTR(MP_QSTR_get_certificate),
+ MP_ROM_PTR(&mod_trezorcrypto_mcu_get_certificate_obj)},
+ {MP_ROM_QSTR(MP_QSTR_sign), MP_ROM_PTR(&mod_trezorcrypto_mcu_sign_obj)},
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_mcu_globals,
+ mod_trezorcrypto_mcu_globals_table);
+
+STATIC const mp_obj_module_t mod_trezorcrypto_mcu_module = {
+ .base = {&mp_type_module},
+ .globals = (mp_obj_dict_t *)&mod_trezorcrypto_mcu_globals,
+};
+
+#endif // USE_MCU_ATTESTATION
diff --git a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto.c b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto.c
index 5077cfa9..542164f1 100644
--- a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto.c
+++ b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto.c
@@ -72,6 +72,9 @@ static void wrapped_ui_wait_callback(uint32_t current, uint32_t total) {
#ifdef USE_TROPIC
#include "modtrezorcrypto-tropic.h"
#endif
+#ifdef USE_MCU_ATTESTATION
+#include "modtrezorcrypto-mcu.h"
+#endif
#if !BITCOIN_ONLY
#include "modtrezorcrypto-cardano.h"
#include "modtrezorcrypto-monero.h"
@@ -141,6 +144,9 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
#if USE_TROPIC
{MP_ROM_QSTR(MP_QSTR_tropic), MP_ROM_PTR(&mod_trezorcrypto_tropic_module)},
#endif
+#ifdef USE_MCU_ATTESTATION
+ {MP_ROM_QSTR(MP_QSTR_mcu), MP_ROM_PTR(&mod_trezorcrypto_mcu_module)},
+#endif
};
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorcrypto_globals,
mp_module_trezorcrypto_globals_table);
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index d5ce3d64..acd65ba8 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -784,6 +784,8 @@ STATIC const mp_obj_tuple_t mod_trezorutils_version_obj = {
/// """Whether the hardware supports Optiga secure element."""
/// USE_TROPIC: bool
/// """Whether the hardware supports Tropic Square secure element."""
+/// USE_MCU_ATTESTATION: bool
+/// """Whether the hardware supports MCU attestation signing and certificate."""
/// USE_TOUCH: bool
/// """Whether the hardware supports touch screen."""
/// USE_BUTTON: bool
@@ -979,6 +981,11 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
#else
{MP_ROM_QSTR(MP_QSTR_USE_TROPIC), mp_const_false},
#endif
+#ifdef USE_MCU_ATTESTATION
+ {MP_ROM_QSTR(MP_QSTR_USE_MCU_ATTESTATION), mp_const_true},
+#else
+ {MP_ROM_QSTR(MP_QSTR_USE_MCU_ATTESTATION), mp_const_false},
+#endif
#ifdef USE_TOUCH
{MP_ROM_QSTR(MP_QSTR_USE_TOUCH), mp_const_true},
#else
diff --git a/core/mocks/generated/trezorcrypto/mcu.pyi b/core/mocks/generated/trezorcrypto/mcu.pyi
new file mode 100644
index 00000000..c2b168cf
--- /dev/null
+++ b/core/mocks/generated/trezorcrypto/mcu.pyi
@@ -0,0 +1,16 @@
+from typing import *
+from buffer_types import *
+
+
+# upymod/modtrezorcrypto/modtrezorcrypto-mcu.h
+def get_certificate() -> bytes:
+ """
+ Return MCU device certificate.
+ """
+
+
+# upymod/modtrezorcrypto/modtrezorcrypto-mcu.h
+def sign(challenge: AnyBytes) -> bytes:
+ """
+ Sign challenge bytes with MCU device attestation key.
+ """
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index d2d6ada4..44fb593c 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -266,6 +266,8 @@ USE_OPTIGA: bool
"""Whether the hardware supports Optiga secure element."""
USE_TROPIC: bool
"""Whether the hardware supports Tropic Square secure element."""
+USE_MCU_ATTESTATION: bool
+"""Whether the hardware supports MCU attestation signing and certificate."""
USE_TOUCH: bool
"""Whether the hardware supports touch screen."""
USE_BUTTON: bool
diff --git a/core/site_scons/site_tools/micropython/__init__.py b/core/site_scons/site_tools/micropython/__init__.py
index 90d45949..f06d3964 100644
--- a/core/site_scons/site_tools/micropython/__init__.py
+++ b/core/site_scons/site_tools/micropython/__init__.py
@@ -50,6 +50,7 @@ def generate(env):
# replace "utils.BITCOIN_ONLY" or "utils.USE_<FEATURE>" with literal constant (True/False)
# so the compiler can optimize out the things we don't want
backlight = env["backlight"]
+ mcu_attestation = env["TREZOR_MODEL"] == "T3W1"
ble = env["use_ble"]
btc_only = env["bitcoin_only"] == "1"
button = env["use_button"]
@@ -73,6 +74,7 @@ def generate(env):
rf"-e 's/utils\.USE_BACKLIGHT/{backlight}/g'",
rf"-e 's/utils\.USE_BLE/{ble}/g'",
rf"-e 's/utils\.BITCOIN_ONLY/{btc_only}/g'",
+ rf"-e 's/utils\.USE_MCU_ATTESTATION/{mcu_attestation}/g'",
rf"-e 's/utils\.USE_BUTTON/{button}/g'",
rf"-e 's/utils\.USE_HAPTIC/{haptic}/g'",
rf"-e 's/utils\.EMULATOR/{emulator}/g'",
diff --git a/core/src/apps/management/authenticate_device.py b/core/src/apps/management/authenticate_device.py
index 67ebd619..2aa6cf64 100644
--- a/core/src/apps/management/authenticate_device.py
+++ b/core/src/apps/management/authenticate_device.py
@@ -61,6 +61,19 @@ async def authenticate_device(msg: AuthenticateDevice) -> AuthenticityProof:
r = BufferReader(tropic.get_user_data(tropic.DEVICE_CERT_INDEX))
tropic_certificates = parse_cert_chain(r)
+ mcu_certificates = None
+ mcu_signature = None
+ if utils.USE_MCU_ATTESTATION:
+ from trezor.crypto import mcu
+
+ try:
+ mcu_signature = mcu.sign(challenge_bytes)
+ except RuntimeError:
+ raise wire.ProcessError("MCU signing failed.")
+
+ r = BufferReader(mcu.get_certificate())
+ mcu_certificates = parse_cert_chain(r)
+
if not utils.DISABLE_ANIMATION:
frame_delay = sleep(60)
for i in range(1, 20):
@@ -74,4 +87,6 @@ async def authenticate_device(msg: AuthenticateDevice) -> AuthenticityProof:
optiga_signature=optiga_signature,
tropic_certificates=tropic_certificates,
tropic_signature=tropic_signature,
+ mcu_certificates=mcu_certificates,
+ mcu_signature=mcu_signature,
)
diff --git a/core/src/trezor/crypto/__init__.py b/core/src/trezor/crypto/__init__.py
index 53867435..2c9951f5 100644
--- a/core/src/trezor/crypto/__init__.py
+++ b/core/src/trezor/crypto/__init__.py
@@ -25,5 +25,8 @@ if utils.USE_OPTIGA:
if utils.USE_TROPIC:
from trezorcrypto import tropic # noqa: F401
+if utils.USE_MCU_ATTESTATION:
+ from trezorcrypto import mcu # noqa: F401
+
if utils.USE_THP:
from trezorcrypto import elligator2 # noqa: F401
diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py
index f416e763..e2874e76 100644
--- a/core/src/trezor/utils.py
+++ b/core/src/trezor/utils.py
@@ -27,6 +27,7 @@ from trezorutils import ( # noqa: F401
USE_BUTTON,
USE_DBG_CONSOLE,
USE_HAPTIC,
+ USE_MCU_ATTESTATION,
USE_N4W1,
USE_NRF,
USE_OPTIGA,
diff --git a/core/src/trezor/wire/thp/memory_manager.py b/core/src/trezor/wire/thp/memory_manager.py
index bb6d2828..726bfc1c 100644
--- a/core/src/trezor/wire/thp/memory_manager.py
+++ b/core/src/trezor/wire/thp/memory_manager.py
@@ -9,7 +9,8 @@ from .writer import MESSAGE_TYPE_LENGTH
if TYPE_CHECKING:
from buffer_types import AnyBuffer
-_PROTOBUF_BUFFER_SIZE = const(8192)
+# Reserve 8.5 kB. AuthenticityProof requires about 8500 bytes.
+_PROTOBUF_BUFFER_SIZE = const(8704)
if __debug__:
from trezor import log
diff --git a/core/tests/test_trezor.wire.thp.channel.py b/core/tests/test_trezor.wire.thp.channel.py
index 2bd631b9..567c9df7 100644
--- a/core/tests/test_trezor.wire.thp.channel.py
+++ b/core/tests/test_trezor.wire.thp.channel.py
@@ -49,8 +49,6 @@ class TestTrezorHostProtocolChannel(TestCaseWithContext):
"""
reassembler = Reassembler(ThpBuffer())
read_buffer = reassembler.thp_read_buf
- # Check constant has not been modified
- self.assertEqual(_PROTOBUF_BUFFER_SIZE, 8192)
# Should pass
for buffer_len in (0, 5, 100, 4096, _PROTOBUF_BUFFER_SIZE):
Why this scored 21/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.