feat(core/prodtest): Check MCU device certificate chain.
What changed, and why it matters
This commit adds a verification step in the Trezor hardware wallet's factory production-test tool before writing a device certificate to secure storage. It now checks that the supplied certificate chain is cryptographically valid and matches the device's own MCU authentication key, preventing a malformed or mismatched certificate from being permanently written during manufacturing.
No immediate action required for end users. For Trezor manufacturing/QA, ensure production-test tooling is updated to include this validation and that the fixed zero-challenge design is acceptable for the intended threat model. Review whether the constant challenge could permit replay or pre-computation in any downstream verification logic.
Security signals we found
Adds cryptographic validation before writing a device certificate to secure storage
Uses a fixed zero challenge for certificate-chain verification
Clears sensitive key material from stack with memzero after signing
Prevents writing a certificate that does not match the MCU authentication key
Located in production-test tooling, not end-user wallet runtime
Evidence from the diff
In core/embed/projects/prodtest/cmd/prodtest_secrets.c, a new check_device_cert_chain() helper is added and called inside prodtest_secrets_certdev_write() before secret_write(). The helper loads the MCU device-authentication private key via secret_key_mcu_device_auth(), signs a fixed all-zero challenge with Ed25519, and passes the signature and challenge to check_cert_chain() to validate the caller-supplied certificate chain. If validation fails, the write is aborted. This is a defensive hardening change in the production-test (prodtest) firmware, not the main wallet firmware, and is excluded from emulator builds.
Changed components
core/embed/projects/prodtest/cmd/prodtest_secrets.cTrezor production-test firmwareMCU device certificate provisioning flowInspect captured patch +32 / −0
diff --git a/core/embed/projects/prodtest/cmd/prodtest_secrets.c b/core/embed/projects/prodtest/cmd/prodtest_secrets.c
index 8e0ba192..b13777b4 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_secrets.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_secrets.c
@@ -28,6 +28,7 @@
#include <sec/secret.h>
#include <sec/secret_keys.h>
+#include "common.h"
#include "memzero.h"
#include "rand.h"
#include "secbool.h"
@@ -162,6 +163,31 @@ cleanup:
memzero(mcu_private, sizeof(mcu_private));
}
+#ifndef TREZOR_EMULATOR
+static bool check_device_cert_chain(cli_t* cli, const uint8_t* chain,
+ size_t chain_size) {
+ ed25519_secret_key mcu_private = {0};
+ if (secret_key_mcu_device_auth(mcu_private) != sectrue) {
+ cli_error(cli, CLI_ERROR, "`secret_key_mcu_device_auth()` failed.");
+ return false;
+ }
+
+ // The challenge is intentionally constant zero.
+ uint8_t challenge[CHALLENGE_SIZE] = {0};
+ ed25519_signature signature = {0};
+ ed25519_sign(challenge, sizeof(challenge), mcu_private, signature);
+ memzero(mcu_private, sizeof(mcu_private));
+
+ if (!check_cert_chain(cli, chain, chain_size, signature, sizeof(signature),
+ challenge)) {
+ // Error returned by check_cert_chain().
+ return false;
+ }
+
+ return true;
+}
+#endif
+
static void prodtest_secrets_certdev_write(cli_t* cli) {
if (cli_arg_count(cli) != 1) {
cli_error_arg_count(cli);
@@ -187,6 +213,12 @@ static void prodtest_secrets_certdev_write(cli_t* cli) {
prefixed_certificate[0] = (certificate_length >> 8) & 0xFF;
prefixed_certificate[1] = certificate_length & 0xFF;
+ if (!check_device_cert_chain(cli, &prefixed_certificate[prefix_length],
+ certificate_length)) {
+ // Error returned by check_device_cert_chain().
+ return;
+ }
+
secret_write(prefixed_certificate, SECRET_MCU_DEVICE_CERT_OFFSET,
sizeof(prefixed_certificate));
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.