Skip to content

Commit f1bff16

Browse files
committed
feat(core/prodtest): implement tropic prodtest
1 parent d03974f commit f1bff16

File tree

11 files changed

+1594
-134
lines changed

11 files changed

+1594
-134
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add Tropic provisioning commands.

core/embed/projects/prodtest/README.md

Lines changed: 172 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,33 @@ Lock successful
666666
OK
667667
```
668668

669+
### secrets-get-mcu-device-key
670+
Returns a cryptogram that encrypts and authenticates the device attestation public key stored in MCU. The commands `secrets-init` and `secure-channel-handshake-2` must be executed before calling this command.
671+
672+
Example:
673+
```
674+
secrets-get-mcu-device-key
675+
OK 638c8a83ddc8fd84cddf5a0a4fa3d9615146cd341685dca942bab1132c2bc99b
676+
```
677+
678+
### secrets-certdev-write
679+
Writes the X.509 device attestation certificate issued by the Trezor Company for the attestation key stored in MCU.
680+
681+
Example:
682+
```
683+
secrets-certdev-write <hexadecimal string>
684+
OK
685+
```
686+
687+
### secrets-certdev-read
688+
Retrieves the X.509 device attestation certificate issued by the Trezor Company for the attestation key stored in MCU.
689+
690+
Example:
691+
```
692+
secrets-certdev-read
693+
OK <hexadecimal string>
694+
```
695+
669696
### optiga-pair
670697
Writes the pairing secret to the Optiga chip to pair it with the MCU. The command `secrets-init` must be executed before calling this command.
671698

@@ -693,17 +720,17 @@ optiga-certinf-read
693720
OK <hexadecimal string>
694721
```
695722

696-
### optiga-certinf-write
697-
Writes the X.509 certificate issued by the Trezor Company for the device.
723+
### optiga-certdev-write
724+
Writes the X.509 certificate issued by the Trezor Company for the device attestation key stored in Optiga.
698725

699726
Example:
700727
```
701-
optiga-certinf-write <hexadecimal string>
728+
optiga-certdev-write <hexadecimal string>
702729
OK
703730
```
704731

705-
### optiga-certdev-red
706-
Retrieves the X.509 certificate issued by the Trezor Company for the device.
732+
### optiga-certdev-read
733+
Retrieves the X.509 certificate issued by the Trezor Company for the device attestation key stored in Optiga.
707734

708735
Example:
709736
```
@@ -712,7 +739,7 @@ OK <hexadecimal string>
712739
```
713740

714741
### optiga-certfido-write
715-
Writes the X.509 certificate issued by the Trezor Company for the FIDO attestation key.
742+
Writes the X.509 certificate issued by the Trezor Company for the FIDO attestation key stored in Optiga.
716743

717744
Example:
718745
```
@@ -721,7 +748,7 @@ OK
721748
```
722749

723750
### optiga-certfido-read
724-
Retrieves the X.509 certificate issued by the Trezor Company for the FIDO attestation key.
751+
Retrieves the X.509 certificate issued by the Trezor Company for the FIDO attestation key stored in Optiga.
725752

726753
Example:
727754
```
@@ -986,6 +1013,144 @@ tropic-update-fw
9861013
OK
9871014
```
9881015

1016+
### tropic-certtropic-read
1017+
1018+
Reads the X.509 certificate issued by Tropic Square for the Tropic chip.
1019+
1020+
Example:
1021+
```
1022+
tropic-certtropic-read
1023+
OK 308201CB30820151A00302010202100200110308861906100F32000000045B300A06082A8648CE3D0403033047310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3119301706035504030C1054524F50494330312D54204341207631301E170D3235303730313130353533325A170D3435303730313130353533325A30173115301306035504030C0C54524F504943303120655345302A300506032B656E032100F582E78C4ECCB186D72A29B6B54E8CAD931C765DBA0C3EDE9405602CB1065246A37E307C300C0603551D130101FF04023000300E0603551D0F0101FF040403020308301F0603551D2304183016801433C711060CE80513B5677B019650644E3B43FAE7303B0603551D1F043430323030A02EA02C862A687474703A2F2F706B692E74726F7069637371756172652E636F6D2F6C332F7430312D5476312E63726C300A06082A8648CE3D0403030368003065023100C46E44F9D1FE26A4DC8AC659D1B6A9A82CEEBE9D283726633053FE410FF665073B7FB6ECE235FD8AB7F87336DDBCF96202300F919622C0A1CF6D00CF43CE4229AC44548055030566E8E03CA98B15D6B29B04DF231B9BF7006A9E28C15E88B07141893082025E308201E4A00302010202027531300A06082A8648CE3D0403033045310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3117301506035504030C0E54524F50494330312043412076313020170D3235303333313132303833305A180F32303630303333313132303833305A3047310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3119301706035504030C1054524F50494330312D542043412076313076301006072A8648CE3D020106052B8104002203620004A70C3273AE3227DC767EF0293D95CC106691E5BC9AA6C0282BAA8FD4B37CFAC30FEE0D879C32D8D9CE9B0BD7924B5C10097B8C4A5E7ED68D690185E3D128161256C01033C0293DE39A7188A72CFF9EEAF5B3B5DEE898F954C4F226C2ADE70BC6A381A230819F301D0603551D0E0416041433C711060CE80513B5677B019650644E3B43FAE730120603551D130101FF040830060101FF020100300E0603551D0F0101FF040403020106301F0603551D2304183016801443BAB7BDA7CDE728945CF142CBD2F9CD5588A93F30390603551D1F04323030302EA02CA02A8628687474703A2F2F706B692E74726F7069637371756172652E636F6D2F6C322F74303176312E63726C300A06082A8648CE3D0403030368003065023014AEC525E5E8311B5D6312CF0EBB2286700552EEBA32D641672C20F02A612B77E9FC3709C9657CEC6D82D6CDBDDE57C4023100BB9B77CCBBD9DE11086481D4BA9772C38743591E722B9E4D08A89940D879DA2447A55C15F84175C479946326E0F482AF3082028B308201ECA00302010202020BB9300A06082A8648CE3D040304304F310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3121301F06035504030C1854726F7069632053717561726520526F6F742043412076313020170D3235303333313132303832395A180F32303635303333313132303832395A3045310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3117301506035504030C0E54524F50494330312043412076313076301006072A8648CE3D020106052B81040022036200042301BE5B6ED9A858153F57C6BEBC9F37B858BC2874DDC90C1041BE6D04E7BBF24A7968F2E51173D0ACAC892E65E4FC03EA5BC4381A60154D7CD7CC6DF94591650F5FDC008919157314FC1F8D8295F1A10571DD1573E868BFECA96C92CCBB816FA381A230819F301D0603551D0E0416041443BAB7BDA7CDE728945CF142CBD2F9CD5588A93F30120603551D130101FF040830060101FF020101300E0603551D0F0101FF040403020106301F0603551D230418301680143C18AF711A6699B37914E363963FE25CF304B3BF30390603551D1F04323030302EA02CA02A8628687474703A2F2F706B692E74726F7069637371756172652E636F6D2F6C312F74737276312E63726C300A06082A8648CE3D04030403818C00308188024200BCD02D464329F3FC7DC81723B0C26437E35C2B49782BAE97432789F508B5A220240E6E3E4D12C15C3BDB15A8D3F90CDD19071E2227C4898220B2BEF584B2C20F8F024201EB854F05F9A2C5B466D798FE627C539B98703531735F7AB49546FE5CFB9DF0BF3B6985D700EFBC36DF3FF01692F0ECE98BB8DB2FBB9BF40913EA87EA121A7AD2E730820258308201BBA0030201020202012D300A06082A8648CE3D040304304F310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3121301F06035504030C1854726F7069632053717561726520526F6F742043412076313020170D3235303333313132303832355A180F32303735303333313132303832355A304F310B300906035504061302435A311D301B060355040A0C1454726F7069632053717561726520732E722E6F2E3121301F06035504030C1854726F7069632053717561726520526F6F7420434120763130819B301006072A8648CE3D020106052B8104002303818600040187CCEA62837E23092D8A7135789FCC6FBC3D35E79FC01F4F498FC5C2C409CE772F901340090403E8BA4D97E13F1E7594AC6D2F51FD2239F8D457769F378440A18000712BF16A48EA2025837BEFD0502A562FD93941D52CC40ED9553CA79B145BA585F32492BFD792EB96D949D31676CD099F19CE8848697B8C3430AF016FED985E1EB4A3423040301D0603551D0E041604143C18AF711A6699B37914E363963FE25CF304B3BF300F0603551D130101FF040530030101FF300E0603551D0F0101FF040403020106300A06082A8648CE3D04030403818A0030818602416841837339337C182A4EE896CBFD5DA5925F0026E7A6FA3DEE61F49A46B5D9856858D3D86501BE64B0F2F33B05D856DE96F57B947F49E720E875090B30C337791802412FDDB68D166510451FE4C62DBAE0CCD952DC34E03AE6617818CCD0EA28A9DFF045AA13A248A5F066B51139C9BEF471DD004DAC4F78DB56CF7B3E8D6F8F87D048D2
1024+
```
1025+
1026+
### tropic-lock-check
1027+
1028+
Returns 'YES' if the Tropic chip has been locked, otherwise returns 'NO'.
1029+
1030+
Example:
1031+
```
1032+
tropic-lock-check
1033+
OK YES
1034+
```
1035+
1036+
### tropic-pair
1037+
1038+
Pairs the MCU with 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 `secrets-init` must be executed before calling this command.
1039+
1040+
Example:
1041+
```
1042+
tropic-pair
1043+
OK
1044+
```
1045+
1046+
### tropic-get-access-credential
1047+
1048+
Returns a cryptogram that encrypts and authenticates the Tropic pairing private key and authenticates the Tropic public key. The commands `secrets-init` and `secure-channel-handshake-2` must be executed before calling this command.
1049+
1050+
Example:
1051+
```
1052+
tropic-get-access-credential
1053+
OK 03ca0e9d74ef59fa80a06161f3d2fceeb3e0c5e2db8182526d337aac78bad2d2ce4cacf05cdcd879843bcc43ed330199
1054+
```
1055+
1056+
### tropic-get-fido-masking-key
1057+
1058+
Returns a cryptogram that encrypts and authenticates the FIDO masking key for the Tropic chip. The commands `secrets-init` and `secure-channel-handshake-2` must be executed before calling this command.
1059+
1060+
Example:
1061+
```
1062+
tropic-get-fido-masking-key
1063+
OK dc106118a32feeef8d9211f54b9c8e9d571abe4cb104dc4ab087531cfee4574283ccf9c6f45e68be712f630d72d4999c
1064+
```
1065+
1066+
### tropic-handshake
1067+
1068+
Establishes a secure channel with the Tropic chip. Expects a handshake request as input, returns a handshake response.
1069+
1070+
```
1071+
tropic-handshake 648724356a6bb22b258557927287af52133a27b7317d3c919db23395cae03d853422af
1072+
OK 09ad6ec70806318313c903094ae8fb63698051210dfa540ea7c7f7e588601dac478eee30432063964574879dee93250d8a5049
1073+
```
1074+
1075+
### tropic-send-command
1076+
1077+
Sends a command to the Tropic chip and returns the response. The command `tropic-handshake` must be executed before calling this command.
1078+
1079+
Example:
1080+
```
1081+
tropic-send-command <hexadecimal string>
1082+
OK <hexadecimal string>
1083+
```
1084+
1085+
### tropic-certdev-read
1086+
1087+
Retrieves the X.509 certificate issued by the Trezor Company for the device attestation key stored in Tropic.
1088+
1089+
Example:
1090+
```
1091+
tropic-certdev-read
1092+
OK <hexadecimal string>
1093+
```
1094+
1095+
### tropic-certdev-write
1096+
1097+
Writes the X.509 certificate issued by the Trezor Company for the device attestation key stored in Tropic.
1098+
1099+
Example:
1100+
```
1101+
tropic-certdev-write <hexadecimal string>
1102+
OK <hexadecimal string>
1103+
```
1104+
1105+
### tropic-certfido-read
1106+
1107+
Retrieves the X.509 certificate issued by the Trezor Company for the FIDO attestation key stored in Tropic.
1108+
1109+
Example:
1110+
```
1111+
tropic-certfido-read
1112+
OK <hexadecimal string>
1113+
```
1114+
1115+
### tropic-certfido-write
1116+
1117+
Writes the X.509 certificate issued by the Trezor Company for the FIDO attestation key stored in Tropic.
1118+
1119+
Example:
1120+
```
1121+
tropic-certfido-write <hexadecimal string>
1122+
OK <hexadecimal string>
1123+
```
1124+
1125+
### tropic-lock
1126+
1127+
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.
1128+
1129+
Example:
1130+
```
1131+
tropic-lock
1132+
OK <hexadecimal string>
1133+
```
1134+
1135+
### secure-channel-handshake-1
1136+
1137+
Returns the first handshake message for establishing a secure channel between the device and HSM.
1138+
1139+
Example:
1140+
```
1141+
secure-channel-handshake-1
1142+
OK 1e85285cbf805d0418be1f502a325806f68fa07c78fd63b7b960b2d0416f8b49
1143+
```
1144+
1145+
### secure-channel-handshake-2
1146+
1147+
Establishes a secure channel between the device and HSM. Expects the second handshake message as input. The command `secure-channel-handshake-1` must be executed before calling this command.
1148+
1149+
Example:
1150+
```
1151+
secure-channel-handshake-2 e08e84b91413ad8f7b07853c8ce4c1b5547a12d9dd65f30e3adaa1e2398e0359bd7ba0e9fb2c64130c25d56abb811f72
1152+
OK
1153+
```
9891154

9901155
### wpc-info
9911156
Retrieves detailed information from the wireless power receiver, including chip identification, firmware version, configuration settings, and error status.
@@ -1127,6 +1292,3 @@ Example:
11271292
rtc-get
11281293
OK 2025 07 03 14 23 00 4
11291294
```
1130-
1131-
1132-

core/embed/projects/prodtest/cmd/common.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ static const uint8_t SUBJECT_COMMON_NAME[] = {
7373
#ifdef TREZOR_MODEL_T3T1
7474
'T', '3', 'T', '1', ' ', 'T', 'r', 'e', 'z', 'o', 'r', ' ', 'S', 'a', 'f', 'e', ' ', '5',
7575
#endif
76+
#ifdef TREZOR_MODEL_T3W1
77+
'T', '3', 'W', '1', ' ', 'T', 'r', 'e', 'z', 'o', 'r', ' ', 'S', 'a', 'f', 'e', ' ', '7',
78+
#endif
7679
};
7780
// clang-format on
7881

core/embed/projects/prodtest/cmd/prodtest_secrets.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
#include "memzero.h"
3232
#include "rand.h"
3333
#include "secbool.h"
34+
#include "secure_channel.h"
35+
36+
#ifndef TREZOR_EMULATOR
37+
#include <trezor_model.h>
38+
#endif
3439

3540
secbool generate_random_secret(uint8_t* secret, size_t length) {
3641
random_buffer(secret, length);
@@ -128,6 +133,97 @@ static void prodtest_secrets_init(cli_t* cli) {
128133
cli_ok(cli, "");
129134
}
130135

136+
#ifdef SECRET_MASTER_KEY_SLOT_SIZE
137+
static void prodtest_secrets_get_mcu_device_key(cli_t* cli) {
138+
if (cli_arg_count(cli) > 0) {
139+
cli_error_arg_count(cli);
140+
return;
141+
}
142+
143+
ed25519_secret_key mcu_private = {0};
144+
if (secret_key_mcu_device_auth(mcu_private) != sectrue) {
145+
cli_error(cli, CLI_ERROR, "`secret_key_mcu_device_auth()` failed.");
146+
return;
147+
}
148+
ed25519_public_key mcu_public = {0};
149+
ed25519_publickey(mcu_private, mcu_public);
150+
151+
uint8_t output[sizeof(ed25519_public_key) + NOISE_TAG_SIZE] = {0};
152+
if (!secure_channel_encrypt(mcu_public, sizeof(mcu_public), NULL, 0,
153+
output)) {
154+
// `secure_channel_handshake_2()` might not have been called
155+
cli_error(cli, CLI_ERROR, "`secure_channel_encrypt()` failed.");
156+
goto cleanup;
157+
}
158+
159+
cli_ok_hexdata(cli, output, sizeof(output));
160+
161+
cleanup:
162+
memzero(mcu_private, sizeof(mcu_private));
163+
}
164+
165+
static void prodtest_secrets_certdev_write(cli_t* cli) {
166+
if (cli_arg_count(cli) > 0) {
167+
cli_error_arg_count(cli);
168+
return;
169+
}
170+
171+
#ifdef TREZOR_EMULATOR
172+
cli_error(cli, CLI_ERROR, "Not implemented");
173+
#else
174+
const size_t prefix_length = 2;
175+
size_t certificate_length = 0;
176+
uint8_t prefixed_certificate[SECRET_MCU_DEVICE_CERT_SIZE] = {0};
177+
if (!cli_arg_hex(cli, "hex-data", prefixed_certificate + prefix_length,
178+
sizeof(prefixed_certificate) - prefix_length,
179+
&certificate_length)) {
180+
if (certificate_length == sizeof(prefixed_certificate) - prefix_length) {
181+
cli_error(cli, CLI_ERROR, "Certificate too long.");
182+
} else {
183+
cli_error(cli, CLI_ERROR, "Hexadecimal decoding error.");
184+
}
185+
return;
186+
}
187+
prefixed_certificate[0] = (certificate_length >> 8) & 0xFF;
188+
prefixed_certificate[1] = certificate_length & 0xFF;
189+
190+
secret_write(prefixed_certificate, SECRET_MCU_DEVICE_CERT_OFFSET,
191+
sizeof(prefixed_certificate));
192+
193+
cli_ok(cli, "");
194+
#endif
195+
}
196+
197+
static void prodtest_secrets_certdev_read(cli_t* cli) {
198+
if (cli_arg_count(cli) > 0) {
199+
cli_error_arg_count(cli);
200+
return;
201+
}
202+
203+
#ifdef TREZOR_EMULATOR
204+
cli_error(cli, CLI_ERROR, "Not implemented");
205+
#else
206+
const size_t prefix_length = 2;
207+
uint8_t prefixed_certificate[SECRET_MCU_DEVICE_CERT_SIZE] = {0};
208+
209+
if (secret_read(prefixed_certificate, SECRET_MCU_DEVICE_CERT_OFFSET,
210+
sizeof(prefixed_certificate)) != sectrue) {
211+
cli_error(cli, CLI_ERROR, "`secret_read()` failed.");
212+
return;
213+
}
214+
215+
size_t certificate_length =
216+
prefixed_certificate[0] << 8 | prefixed_certificate[1];
217+
218+
if (certificate_length > sizeof(prefixed_certificate) - prefix_length) {
219+
cli_error(cli, CLI_ERROR, "Invalid certificate data.");
220+
return;
221+
}
222+
cli_ok_hexdata(cli, prefixed_certificate + prefix_length, certificate_length);
223+
#endif
224+
}
225+
#endif
226+
131227
#ifdef SECRET_LOCK_SLOT_OFFSET
132228
static void prodtest_secrets_lock(cli_t* cli) {
133229
if (cli_arg_count(cli) > 0) {
@@ -160,6 +256,29 @@ PRODTEST_CLI_CMD(
160256
.args = ""
161257
);
162258

259+
#ifdef SECRET_MASTER_KEY_SLOT_SIZE
260+
PRODTEST_CLI_CMD(
261+
.name = "secrets-get-mcu-device-key",
262+
.func = prodtest_secrets_get_mcu_device_key,
263+
.info = "Get MCU device attestation public key",
264+
.args = ""
265+
);
266+
267+
PRODTEST_CLI_CMD(
268+
.name = "secrets-certdev-write",
269+
.func = prodtest_secrets_certdev_write,
270+
.info = "Write the device's X.509 certificate to flash",
271+
.args = "<hex-data>"
272+
);
273+
274+
PRODTEST_CLI_CMD(
275+
.name = "secrets-certdev-read",
276+
.func = prodtest_secrets_certdev_read,
277+
.info = "Read the device's X.509 certificate from flash",
278+
.args = ""
279+
);
280+
#endif
281+
163282
#ifdef SECRET_LOCK_SLOT_OFFSET
164283
PRODTEST_CLI_CMD(
165284
.name = "secrets-lock",

core/embed/projects/prodtest/cmd/prodtest_secure_channel.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ static void prodtest_secure_channel_handshake_2(cli_t* cli) {
5959
}
6060

6161
if (!secure_channel_handshake_2(input)) {
62-
// TODO: Consider distinguishing between cryptography error and state error
63-
cli_error(cli, CLI_ERROR,
64-
"You have to call `secure-channel-handshake-1` first.");
62+
// Either `secure_channel_handshake_1()` has not been called or the keys do
63+
// not match.
64+
cli_error(cli, CLI_ERROR, "`secure_channel_handshake_2()` failed.");
6565
}
6666

6767
cli_ok(cli, "");

0 commit comments

Comments
 (0)