feat(core/prodtest): Implement tropic-keyfido-read.
What changed, and why it matters
This commit adds a new factory-testing command, tropic-keyfido-read, to the Trezor device's production-test firmware. The command lets an authorized tester read out the FIDO attestation public key stored on the Tropic secure chip. It is a read-only diagnostic feature and does not, by itself, expose private keys or change device state. The main security consideration is that it adds another surface in the already-privileged prodtest environment, but it appears consistent with existing read commands and uses the same masking/unmasking pattern.
Treat as a low-risk feature addition. Reviewers should confirm that the prodtest CLI is only accessible in factory/test builds and that the TROPIC_FIDO_KEY_SLOT mapping to ECC_SLOT_1 matches the secure-element provisioning scheme. No urgent security patch is indicated by this commit alone.
Security signals we found
New read-only diagnostic command in production-test firmware
Reads FIDO attestation public key from Tropic secure element slot
Optionally unmasks public key using secret_key_tropic_masking() under SECRET_KEY_MASKING
Does not return private key material or modify Tropic state
Follows existing prodtest command patterns (certfido-read, certdev-read, etc.)
Evidence from the diff
The change introduces prodtest_tropic_keyfido_read() and a helper pubkey_read() in core/embed/projects/prodtest/cmd/prodtest_tropic.c. The helper calls lt_ecc_key_read() against TROPIC_FIDO_KEY_SLOT (now defined as ECC_SLOT_1), validates the curve is P256, optionally unmasks the public key with ecdsa_unmask_public_key() when SECRET_KEY_MASKING is enabled, and prints the 65-byte uncompressed public key as hex. A CLI registration and README documentation are added. No private key material is returned; only the public key is exposed. The command is gated by the prodtest build and CLI access controls already present in that project.
Changed components
core/embed/projects/prodtest/cmd/prodtest_tropic.ccore/embed/projects/prodtest/README.mdcore/embed/projects/prodtest/.changelog.d/5657.addedInspect captured patch +66 / −1
diff --git a/core/embed/projects/prodtest/.changelog.d/5657.added b/core/embed/projects/prodtest/.changelog.d/5657.added
new file mode 100644
index 000000000..f45eca372
--- /dev/null
+++ b/core/embed/projects/prodtest/.changelog.d/5657.added
@@ -0,0 +1 @@
+Add the `tropic-keyfido-read` command.
diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md
index 6ae0acc20..db9c09b41 100644
--- a/core/embed/projects/prodtest/README.md
+++ b/core/embed/projects/prodtest/README.md
@@ -1146,6 +1146,18 @@ tropic-certfido-write <hexadecimal string>
OK <hexadecimal string>
```
+### tropic-keyfido-read
+
+Retrieves the FIDO attestation public key stored in Tropic.
+
+This command can be used to verify that the FIDO attestation key was stored correctly by verifying that the returned string of bytes appears in the FIDO attestation certificate.
+
+Example:
+```
+tropic-keyfido-read
+OK <hexadecimal string>
+```
+
### tropic-lock
Configures the Tropic chip. This command is idempotent, meaning it can be called multiple times without changing the state of the device. This command is irreversible and cannot be undone. The command `tropic-pair` must be executed before calling this command.
diff --git a/core/embed/projects/prodtest/cmd/prodtest_tropic.c b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
index 888f5620d..02f5e2215 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -29,7 +29,9 @@
#include <sec/secret.h>
#include <sec/secret_keys.h>
+#include "ecdsa.h"
#include "memzero.h"
+#include "nist256p1.h"
#include "common.h"
#include "fw_CPU.h"
@@ -51,10 +53,10 @@
#define TROPIC_FIDO_CERT_FIRST_SLOT 0
#define TROPIC_FIDO_CERT_SLOTS_COUNT 3
+#define TROPIC_FIDO_KEY_SLOT ECC_SLOT_1
#define TROPIC_DEV_CERT_FIRST_SLOT 3
#define TROPIC_DEV_CERT_SLOTS_COUNT 3
#define TROPIC_DEV_KEY_SLOT 0
-#define TROPIC_FIDO_KEY_SLOT 1
typedef enum {
TROPIC_HANDSHAKE_STATE_0, // Handshake has not been initiated yet
@@ -1577,6 +1579,49 @@ static void prodtest_tropic_certdev_read(cli_t* cli) {
cert_read(cli, TROPIC_DEV_CERT_FIRST_SLOT, TROPIC_DEV_CERT_SLOTS_COUNT);
}
+static void pubkey_read(cli_t* cli, ecc_slot_t slot,
+ const uint8_t masking_key[ECDSA_PRIVATE_KEY_SIZE]) {
+ if (cli_arg_count(cli) > 0) {
+ cli_error_arg_count(cli);
+ return;
+ }
+
+ uint8_t public_key[ECDSA_PUBLIC_KEY_SIZE] = {0x04};
+ lt_ecc_curve_type_t curve_type = 0;
+ ecc_key_origin_t origin = 0;
+ lt_ret_t ret =
+ lt_ecc_key_read(tropic_get_handle(), slot,
+ &public_key[1], &curve_type, &origin);
+ if (ret != LT_OK || curve_type != CURVE_P256) {
+ cli_error(cli, CLI_ERROR, "lt_ecc_key_read error %d.", ret);
+ return;
+ }
+
+ if (masking_key != NULL) {
+ if (ecdsa_unmask_public_key(&nist256p1, masking_key, public_key,
+ public_key) != 0) {
+ cli_error(cli, CLI_ERROR, "key unmasking error");
+ return;
+ }
+ }
+
+ cli_ok_hexdata(cli, public_key, sizeof(public_key));
+}
+
+static void prodtest_tropic_keyfido_read(cli_t* cli) {
+#ifdef SECRET_KEY_MASKING
+ uint8_t masking_key[ECDSA_PRIVATE_KEY_SIZE] = {0};
+ if (secret_key_tropic_masking(masking_key) != sectrue) {
+ cli_error(cli, CLI_ERROR, "masking key not available");
+ return;
+ }
+ pubkey_read(cli, TROPIC_FIDO_KEY_SLOT, masking_key);
+ memzero(masking_key, sizeof(masking_key));
+#else
+ pubkey_read(cli, TROPIC_FIDO_KEY_SLOT, NULL);
+#endif // SECRET_KEY_MASKING
+}
+
static void prodtest_tropic_update_fw(cli_t* cli) {
#define FW_APP_UPDATE_BANK FW_BANK_FW1
#define FW_SPECT_UPDATE_BANK FW_BANK_SPECT1
@@ -1795,6 +1840,13 @@ PRODTEST_CLI_CMD(
.args = "<hex-data>"
);
+PRODTEST_CLI_CMD(
+ .name = "tropic-keyfido-read",
+ .func = prodtest_tropic_keyfido_read,
+ .info = "Read the FIDO public key from Tropic.",
+ .args = ""
+);
+
PRODTEST_CLI_CMD(
.name = "tropic-lock",
.func = prodtest_tropic_lock,
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.