feat(core): Use ML-DSA-44 for MCU device attestation key.

[no changelog]
This commit is contained in:
Andrew Kozlik
2025-08-31 01:23:52 +02:00
committed by Andrew Kozlik
parent 58245dd51d
commit ba51fa46d3
12 changed files with 133 additions and 39 deletions

4
.gitmodules vendored
View File

@@ -41,3 +41,7 @@
path = vendor/sphincsplus
url = https://github.com/sphincs/sphincsplus/
branch = consistent-basew
[submodule "vendor/mldsa-native"]
path = vendor/mldsa-native
url = https://github.com/pq-code-package/mldsa-native.git
ignore = untracked

View File

@@ -144,6 +144,17 @@ if 'boot_ucb' in FEATURES_AVAILABLE:
'vendor/sphincsplus/ref/wots.c',
]
SOURCE_MLDSA = [
'vendor/mldsa-native/mldsa/fips202/fips202.c',
'vendor/mldsa-native/mldsa/fips202/fips202x4.c',
'vendor/mldsa-native/mldsa/fips202/keccakf1600.c',
'vendor/mldsa-native/mldsa/ntt.c',
'vendor/mldsa-native/mldsa/packing.c',
'vendor/mldsa-native/mldsa/poly.c',
'vendor/mldsa-native/mldsa/polyvec.c',
'vendor/mldsa-native/mldsa/sign.c',
]
# modtrezorui
CPPPATH_MOD += [
'vendor/micropython/lib/uzlib'
@@ -303,6 +314,7 @@ obj_program.extend(env.Object(source=SOURCE_MOD))
obj_program.extend(env.Object(source=SOURCE_MOD_CRYPTO, CCFLAGS='$CCFLAGS -ftrivial-auto-var-init=zero'))
obj_program.extend(env.Object(source=SOURCE_PRODTEST))
obj_program.extend(env.Object(source=SOURCE_HAL))
obj_program.extend(env.Object(source=SOURCE_MLDSA))
if 'boot_ucb' in FEATURES_AVAILABLE:
obj_program += env.Object(

View File

@@ -106,6 +106,16 @@ if FEATURE_FLAGS["AES_GCM"]:
'vendor/trezor-crypto/aes/aesgcm.c',
]
SOURCE_MLDSA = [
'vendor/mldsa-native/mldsa/fips202/fips202.c',
'vendor/mldsa-native/mldsa/fips202/fips202x4.c',
'vendor/mldsa-native/mldsa/fips202/keccakf1600.c',
'vendor/mldsa-native/mldsa/ntt.c',
'vendor/mldsa-native/mldsa/packing.c',
'vendor/mldsa-native/mldsa/poly.c',
'vendor/mldsa-native/mldsa/polyvec.c',
'vendor/mldsa-native/mldsa/sign.c',
]
# modtrezorui
CPPPATH_MOD += [
@@ -287,6 +297,7 @@ obj_program += env.Object(source=SOURCE_MOD)
obj_program += env.Object(source=SOURCE_MOD_CRYPTO, CCFLAGS='$CCFLAGS -ftrivial-auto-var-init=zero')
obj_program += env.Object(source=SOURCE_PRODTEST)
obj_program += env.Object(source=SOURCE_HAL)
obj_program += env.Object(source=SOURCE_MLDSA)
program_elf = env.Command(
target='prodtest.elf',

View File

@@ -45,7 +45,7 @@
#define SECRET_KEY_SLOT_2_PUBLIC 1
#define SECRET_MCU_DEVICE_CERT_OFFSET 0x870
#define SECRET_MCU_DEVICE_CERT_SIZE 0x400
#define SECRET_MCU_DEVICE_CERT_SIZE 0x1000
#define SECRET_LOCK_SLOT_OFFSET 0x1FF0
#define SECRET_LOCK_SLOT_LEN 0x10

View File

@@ -32,6 +32,8 @@
#include "sha2.h"
#include "string.h"
#include <../vendor/mldsa-native/mldsa/sign.h>
// Identifier of context-specific constructed tag 3, which is used for
// extensions in X.509.
#define DER_X509_EXTENSIONS 0xa3
@@ -59,6 +61,12 @@ static const uint8_t EDDSA_25519[] = {
0x2b, 0x65, 0x70, // corresponds to EdDSA 25519 in X.509
};
static const uint8_t MLDSA44[] = {
0x30, 0x0b, // a sequence of 11 bytes
0x06, 0x09, // an OID of 9 bytes
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, // corresponds to id-ml-dsa-44 in X.509
};
static const uint8_t OID_COMMON_NAME[] = {
0x06, 0x03, // an OID of 3 bytes
0x55, 0x04, 0x03, // corresponds to commonName in X.509
@@ -87,7 +95,11 @@ static const uint8_t SUBJECT_COMMON_NAME[] = {
};
// clang-format on
typedef enum { ALG_ID_ECDSA_P256_WITH_SHA256, ALG_ID_EDDSA_25519 } alg_id_t;
typedef enum {
ALG_ID_ECDSA_P256_WITH_SHA256,
ALG_ID_EDDSA_25519,
ALG_ID_MLDSA44
} alg_id_t;
static bool get_algorithm(DER_ITEM* alg, alg_id_t* alg_id) {
if (alg->buf.size == sizeof(ECDSA_P256_WITH_SHA256) &&
@@ -103,6 +115,12 @@ static bool get_algorithm(DER_ITEM* alg, alg_id_t* alg_id) {
return true;
}
if (alg->buf.size == sizeof(MLDSA44) &&
memcmp(alg->buf.data, MLDSA44, sizeof(MLDSA44)) == 0) {
*alg_id = ALG_ID_MLDSA44;
return true;
}
return false;
}
@@ -275,6 +293,19 @@ static bool verify_signature(alg_id_t alg_id, const uint8_t* pub_key,
return true;
}
if (alg_id == ALG_ID_MLDSA44) {
if (pub_key_size != CRYPTO_PUBLICKEYBYTES) {
return false;
}
if (crypto_sign_verify(sig, sig_size, msg, msg_size, (const uint8_t*)"", 0,
pub_key) != 0) {
return false;
}
return true;
}
return false;
}

View File

@@ -38,6 +38,8 @@
#include <trezor_model.h>
#endif
#include <../vendor/mldsa-native/mldsa/sign.h>
secbool generate_random_secret(uint8_t* secret, size_t length) {
random_buffer(secret, length);
@@ -141,15 +143,20 @@ static void prodtest_secrets_get_mcu_device_key(cli_t* cli) {
return;
}
ed25519_secret_key mcu_private = {0};
if (secret_key_mcu_device_auth(mcu_private) != sectrue) {
uint8_t seed[MLDSA_SEEDBYTES] = {0};
if (secret_key_mcu_device_auth(seed) != sectrue) {
cli_error(cli, CLI_ERROR, "`secret_key_mcu_device_auth()` failed.");
return;
goto cleanup;
}
ed25519_public_key mcu_public = {0};
ed25519_publickey(mcu_private, mcu_public);
uint8_t output[sizeof(ed25519_public_key) + NOISE_TAG_SIZE] = {0};
uint8_t mcu_public[CRYPTO_PUBLICKEYBYTES] = {0};
uint8_t mcu_private[CRYPTO_SECRETKEYBYTES] = {0};
if (crypto_sign_keypair_internal(mcu_public, mcu_private, seed) != 0) {
cli_error(cli, CLI_ERROR, "`crypto_sign_keypair_internal()` failed.");
goto cleanup;
}
uint8_t output[sizeof(mcu_public) + NOISE_TAG_SIZE] = {0};
if (!secure_channel_encrypt(mcu_public, sizeof(mcu_public), NULL, 0,
output)) {
// `secure_channel_handshake_2()` might not have been called
@@ -160,31 +167,56 @@ static void prodtest_secrets_get_mcu_device_key(cli_t* cli) {
cli_ok_hexdata(cli, output, sizeof(output));
cleanup:
memzero(seed, sizeof(seed));
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) {
bool ret = false;
uint8_t seed[MLDSA_SEEDBYTES] = {0};
if (secret_key_mcu_device_auth(seed) != sectrue) {
cli_error(cli, CLI_ERROR, "`secret_key_mcu_device_auth()` failed.");
return false;
goto cleanup;
}
uint8_t mcu_public[CRYPTO_PUBLICKEYBYTES] = {0};
uint8_t mcu_private[CRYPTO_SECRETKEYBYTES] = {0};
if (crypto_sign_keypair_internal(mcu_public, mcu_private, seed) != 0) {
cli_error(cli, CLI_ERROR, "`crypto_sign_keypair_internal()` failed.");
goto cleanup;
}
uint8_t rnd[MLDSA_RNDBYTES] = {0};
random_buffer(rnd, sizeof(rnd));
// The challenge is intentionally constant zero.
const uint8_t ENCODED_EMPTY_CONTEXT_STRING[] = {0, 0};
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;
uint8_t signature[CRYPTO_BYTES] = {0};
size_t siglen = 0;
if (crypto_sign_signature_internal(
signature, &siglen, challenge, sizeof(challenge),
ENCODED_EMPTY_CONTEXT_STRING, sizeof(ENCODED_EMPTY_CONTEXT_STRING),
rnd, mcu_private, 0) != 0) {
cli_error(cli, CLI_ERROR, "`crypto_sign_signature()` failed.");
goto cleanup;
}
return true;
if (!check_cert_chain(cli, chain, chain_size, signature, siglen, challenge)) {
// Error returned by check_cert_chain().
goto cleanup;
}
ret = true;
cleanup:
memzero(seed, sizeof(seed));
memzero(mcu_private, sizeof(mcu_private));
memzero(rnd, sizeof(rnd));
return ret;
}
#endif

View File

@@ -26,7 +26,7 @@
typedef struct cli cli_t;
// Maximum length of command line input (including command, arguments)
#define CLI_LINE_BUFFER_SIZE 4096
#define CLI_LINE_BUFFER_SIZE 8192
// Maximum number of command arguments + 1
#define CLI_MAX_ARGS 64

View File

@@ -27,9 +27,9 @@
#define SECRET_KEY_MASKING
#include <ed25519-donna/ed25519.h>
#include <../vendor/mldsa-native/mldsa/params.h>
secbool secret_key_mcu_device_auth(ed25519_secret_key dest);
secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]);
#endif // SECRET_MASTER_KEY_SLOT_SIZE

View File

@@ -71,17 +71,6 @@ cleanup:
return ret;
}
static secbool secret_key_derive_curve25519(uint8_t slot, uint16_t index,
curve25519_key dest) {
_Static_assert(sizeof(curve25519_key) == SHA256_DIGEST_LENGTH);
secbool ret = secret_key_derive_sym(slot, index, 0, dest);
dest[0] &= 248;
dest[31] &= 127;
dest[31] |= 64;
return ret;
}
#if defined(USE_OPTIGA) || defined(USE_TROPIC)
static secbool secret_key_derive_nist256p1(
uint8_t slot, uint16_t index, uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
@@ -114,9 +103,10 @@ cleanup:
}
#endif
secbool secret_key_mcu_device_auth(ed25519_secret_key dest) {
return secret_key_derive_curve25519(SECRET_PRIVILEGED_MASTER_KEY_SLOT,
KEY_INDEX_MCU_DEVICE_AUTH, dest);
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, dest);
}
#ifdef USE_OPTIGA
@@ -133,6 +123,17 @@ secbool secret_key_optiga_masking(uint8_t dest[ECDSA_PRIVATE_KEY_SIZE]) {
#endif // USE_OPTIGA
#ifdef USE_TROPIC
static secbool secret_key_derive_curve25519(uint8_t slot, uint16_t index,
curve25519_key dest) {
_Static_assert(sizeof(curve25519_key) == SHA256_DIGEST_LENGTH);
secbool ret = secret_key_derive_sym(slot, index, 0, dest);
dest[0] &= 248;
dest[31] &= 127;
dest[31] |= 64;
return ret;
}
secbool secret_key_tropic_public(curve25519_key dest) {
return secret_key_get(SECRET_TROPIC_TROPIC_PUBKEY_SLOT, dest,
sizeof(curve25519_key));

View File

@@ -44,8 +44,9 @@ _Static_assert(sizeof(SECRET_TROPIC_PAIRING_BYTES) == sizeof(curve25519_key),
_Static_assert(sizeof(SECRET_TROPIC_PUBKEY_BYTES) == sizeof(curve25519_key),
"Invalid size of Tropic public key");
secbool secret_key_mcu_device_auth(ed25519_secret_key dest) {
memset(dest, 3, sizeof(ed25519_secret_key));
secbool secret_key_mcu_device_auth(uint8_t dest[MLDSA_SEEDBYTES]) {
_Static_assert(MLDSA_SEEDBYTES == SHA256_DIGEST_LENGTH);
memset(dest, 3, SHA256_DIGEST_LENGTH);
return sectrue;
}

1
core/vendor/mldsa-native vendored Symbolic link
View File

@@ -0,0 +1 @@
../../vendor/mldsa-native/

1
vendor/mldsa-native vendored Submodule

Submodule vendor/mldsa-native added at 6a9e7f315d