Skip to content

Commit e4c1a3d

Browse files
committed
feat(python): Prep for ML-DSA-44 device authenticity check.
[no changelog]
1 parent 4ac0459 commit e4c1a3d

2 files changed

Lines changed: 89 additions & 41 deletions

File tree

python/src/trezorlib/authentication.py

Lines changed: 77 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, types, utils
2727
from cryptography.x509.oid import NameOID, ObjectIdentifier, SignatureAlgorithmOID
2828

29-
from . import device
29+
from . import _root_keys, device
3030
from .client import Session
3131
from .tools import workflow
3232

@@ -41,6 +41,10 @@ def _pk_ed25519(pubkey_hex: str) -> PublicKey:
4141
return Ed25519PublicKey.from_bytes(bytes.fromhex(pubkey_hex))
4242

4343

44+
def _pk_mldsa44(pubkey_hex: str) -> PublicKey:
45+
return Mldsa44PublicKey.from_bytes(bytes.fromhex(pubkey_hex))
46+
47+
4448
CHALLENGE_HEADER = b"AuthenticateDevice:"
4549

4650
OID_TO_NAME = {
@@ -87,6 +91,9 @@ def from_bytes_and_oid(data: bytes, oid: ObjectIdentifier) -> PublicKey:
8791
return EcdsaPublicKey.from_bytes(data, ec.SECP256R1())
8892
elif oid == SignatureAlgorithmOID.ED25519:
8993
return Ed25519PublicKey.from_bytes(data)
94+
# TODO replace after cryptography 47.0.0 is released
95+
elif oid == ObjectIdentifier("2.16.840.1.101.3.4.3.17"):
96+
return Mldsa44PublicKey.from_bytes(data)
9097
else:
9198
raise ValueError("Unsupported key type.")
9299

@@ -96,6 +103,7 @@ def from_public_key(pubkey: types.CertificatePublicKeyTypes) -> PublicKey:
96103
return EcdsaPublicKey(pubkey)
97104
elif isinstance(pubkey, ed25519.Ed25519PublicKey):
98105
return Ed25519PublicKey(pubkey)
106+
# TODO after cryptography 47.0.0 is released
99107
else:
100108
raise ValueError("Unsupported key type.")
101109

@@ -214,12 +222,35 @@ def verify_certificate(self, certificate: x509.Certificate) -> None:
214222
)
215223

216224

225+
class Mldsa44PublicKey(PublicKey):
226+
def __init__(self, pubkey: t.Any) -> None:
227+
self.pubkey = pubkey
228+
229+
@classmethod
230+
def from_bytes(cls, data: bytes) -> Mldsa44PublicKey:
231+
# TODO after cryptography 47.0.0 is released
232+
return cls(None)
233+
234+
def to_bytes(self) -> bytes:
235+
# TODO after cryptography 47.0.0 is released
236+
return bytes()
237+
238+
def verify_message(self, *, signature: bytes, message: bytes) -> None:
239+
# TODO after cryptography 47.0.0 is released
240+
pass
241+
242+
def verify_certificate(self, certificate: x509.Certificate) -> None:
243+
# TODO after cryptography 47.0.0 is released
244+
pass
245+
246+
217247
class RootCertificate(t.NamedTuple):
218248
name: str
219249
device: str
220250
devel: bool
221251
p256_pubkey: PublicKey
222252
ed25519_pubkey: PublicKey | None = None
253+
mldsa44_pubkey: PublicKey | None = None
223254

224255
def pubkey_for_oid(self, oid: ObjectIdentifier) -> PublicKey:
225256
if oid == SignatureAlgorithmOID.ECDSA_WITH_SHA256:
@@ -228,6 +259,11 @@ def pubkey_for_oid(self, oid: ObjectIdentifier) -> PublicKey:
228259
if self.ed25519_pubkey is None:
229260
raise ValueError("ED25519 public key not set.")
230261
return self.ed25519_pubkey
262+
# TODO replace after cryptography 47.0.0 is released
263+
elif oid == ObjectIdentifier("2.16.840.1.101.3.4.3.17"):
264+
if self.mldsa44_pubkey is None:
265+
raise ValueError("ML-DSA-44 public key not set.")
266+
return self.mldsa44_pubkey
231267
else:
232268
raise ValueError("Unsupported key type.")
233269

@@ -238,91 +274,69 @@ def pubkey_for_oid(self, oid: ObjectIdentifier) -> PublicKey:
238274
"Trezor Company",
239275
"Trezor Safe 3",
240276
False,
241-
_pk_p256(
242-
"04ca97480ac0d7b1e6efafe518cd433cec2bf8ab9822d76eafd34363b55d63e60"
243-
"380bff20acc75cde03cffcb50ab6f8ce70c878e37ebc58ff7cca0a83b16b15fa5"
244-
),
277+
_pk_p256(_root_keys.T2B1_DEV_AUTH_ROOT_PROD_P256_HEX),
245278
),
246279
RootCertificate(
247280
# Root production key for T3B1.
248281
"Trezor Company",
249282
"Trezor Safe 3",
250283
False,
251-
_pk_p256(
252-
"045b5c3fdd01f3602092834209b86df0ca86a9faf25cac35c73bf6237d66eb21e"
253-
"afcec3706f1ccd5eb4cc7f2fa1751213eccb1c78389afba89a5788ff31ee46a5d"
254-
),
284+
_pk_p256(_root_keys.T3B1_DEV_AUTH_ROOT_PROD_P256_HEX),
255285
),
256286
RootCertificate(
287+
# Root production key for T3T1.
257288
"Trezor Company",
258289
"Trezor Safe 5",
259290
False,
260-
_pk_p256(
261-
"041854b27fb1d9f65abb66828e78c9dc0ca301e66081ab0c6a4d104f9df1cd0ad"
262-
"5a7c75f77a8c092f55cf825d2abaf734f934c9394d5e75f75a5a06a5ee9be93ae"
263-
),
291+
_pk_p256(_root_keys.T3T1_DEV_AUTH_ROOT_PROD_P256_HEX),
264292
),
265293
RootCertificate(
266294
# Root production keys for T3W1.
267295
"Trezor Company",
268296
"Trezor Safe 7",
269297
False,
270-
_pk_p256(
271-
"040dde0d3e0d4da593fac6fd02a461d0e7eef238aca55c7c50b4e9ec37f387330"
272-
"3b6429ef1c9b78b4411a7dcbbc5dde5225979c1c2da3b073e82b1ed3f5f9825bb"
273-
),
274-
_pk_ed25519("59237acd17134061d655b3f8d624573ca06ce8d862f38ba4e05140ce1d3d609d"),
298+
_pk_p256(_root_keys.T3W1_DEV_AUTH_ROOT_PROD_P256_HEX),
299+
_pk_ed25519(_root_keys.T3W1_DEV_AUTH_ROOT_PROD_ED25519_HEX),
300+
_pk_mldsa44(_root_keys.T3W1_DEV_AUTH_ROOT_PROD_MLDSA44_HEX),
275301
),
276302
RootCertificate(
277303
# Root backup production keys for T3W1.
278304
"Trezor Company",
279305
"Trezor Safe 7",
280306
False,
281-
_pk_p256(
282-
"04c6a673af4ec44b10441b1d78676e15173ad0e36df9f7f2fa1cd819955f20fe3"
283-
"2917b60da5fed3b3aa54a9ab8b3ed27d198b3768cad26eef5935cd87af0af065e"
284-
),
285-
_pk_ed25519("5612606584ee7e0bc313b13f7ac94156bb4cb75bd77585ddbe579301306e85f1"),
307+
_pk_p256(_root_keys.T3W1_DEV_AUTH_ROOT_PROD_BACKUP_P256_HEX),
308+
_pk_ed25519(_root_keys.T3W1_DEV_AUTH_ROOT_PROD_BACKUP_ED25519_HEX),
309+
_pk_mldsa44(_root_keys.T3W1_DEV_AUTH_ROOT_PROD_BACKUP_MLDSA44_HEX),
286310
),
287311
RootCertificate(
288312
# Root debug key for T2B1 and T3B1.
289313
"TESTING ENVIRONMENT. DO NOT USE THIS DEVICE",
290314
"Trezor Safe 3",
291315
True,
292-
_pk_p256(
293-
"047f77368dea2d4d61e989f474a56723c3212dacf8a808d8795595ef38441427c"
294-
"4389bc454f02089d7f08b873005e4c28d432468997871c0bf286fd3861e21e96a"
295-
),
316+
_pk_p256(_root_keys.T2B1_DEV_AUTH_ROOT_DEBUG_P256_HEX),
296317
),
297318
RootCertificate(
319+
# Root debug key for T3T1.
298320
"TESTING ENVIRONMENT. DO NOT USE THIS DEVICE",
299321
"Trezor Safe 5",
300322
True,
301-
_pk_p256(
302-
"04e48b69cd7962068d3cca3bcc6b1747ef496c1e28b5529e34ad7295215ea161d"
303-
"be8fb08ae0479568f9d2cb07630cb3e52f4af0692102da5873559e45e9fa72959"
304-
),
323+
_pk_p256(_root_keys.T3T1_DEV_AUTH_ROOT_DEBUG_P256_HEX),
305324
),
306325
RootCertificate(
307326
# Root debug keys for T3W1.
308327
"TESTING ENVIRONMENT. DO NOT USE THIS DEVICE",
309328
"Trezor Safe 7",
310329
True,
311-
_pk_p256(
312-
"04521192e173a9da4e3023f747d836563725372681eba3079c56ff11b2fc137ab"
313-
"189eb4155f371127651b5594f8c332fc1e9c0f3b80d4212822668b63189706578"
314-
),
330+
_pk_p256(_root_keys.T3W1_DEV_AUTH_ROOT_DEBUG_P256_HEX),
315331
),
316332
RootCertificate(
317333
# Root staging keys for T3W1.
318334
"TESTING ENVIRONMENT. DO NOT USE THIS DEVICE",
319335
"Trezor Safe 7",
320-
False,
321-
_pk_p256(
322-
"0465e88f9b2cea67e8364f0cfcfacd500af24e9040b357beee629ccc4fce1704d"
323-
"1a7ef7284f387708f92ef14600e2caad6894016fee819d623b95d66210c3e7519"
324-
),
325-
_pk_ed25519("cd318dc8405ae4f4144e3284dcb7b0cb0f0c2195c2ca14a0f6fccd9104e32a4b"),
336+
True,
337+
_pk_p256(_root_keys.T3W1_DEV_AUTH_ROOT_STAGING_P256_HEX),
338+
_pk_ed25519(_root_keys.T3W1_DEV_AUTH_ROOT_STAGING_ED25519_HEX),
339+
_pk_mldsa44(_root_keys.T3W1_DEV_AUTH_ROOT_STAGING_MLDSA44_HEX),
326340
),
327341
]
328342

@@ -550,6 +564,7 @@ def authenticate_device(
550564
allow_development_devices: bool = False,
551565
p256_root_pubkey: bytes | PublicKey | None = None,
552566
ed25519_root_pubkey: bytes | PublicKey | None = None,
567+
mldsa44_root_pubkey: bytes | PublicKey | None = None,
553568
) -> None:
554569
if challenge is None:
555570
challenge = secrets.token_bytes(16)
@@ -585,3 +600,24 @@ def authenticate_device(
585600
if optiga_root is not tropic_root:
586601
LOG.error("Certificates issued by different root authorities.")
587602
raise DeviceNotAuthentic
603+
604+
if (
605+
getattr(optiga_root, "mldsa44_pubkey", None) is not None
606+
or mldsa44_root_pubkey is not None
607+
):
608+
if not resp.mcu_signature:
609+
LOG.error("Missing MCU signature.")
610+
raise DeviceNotAuthentic
611+
612+
mcu_root = verify_authentication_response(
613+
challenge,
614+
resp.mcu_signature,
615+
resp.mcu_certificates,
616+
allowlist=allowlist,
617+
allow_development_devices=allow_development_devices,
618+
root_pubkey=mldsa44_root_pubkey,
619+
)
620+
621+
if optiga_root is not mcu_root:
622+
LOG.error("Certificates issued by different root authorities.")
623+
raise DeviceNotAuthentic

python/src/trezorlib/cli/device.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ def _print_auth_data(signature: bytes, certificates: t.Sequence[bytes]) -> None:
401401
@click.option(
402402
"--ed25519-root", type=click.File("rb"), help="Custom root Ed25519 public key."
403403
)
404+
@click.option(
405+
"--mldsa44-root", type=click.File("rb"), help="Custom root ML-DSA-44 public key."
406+
)
404407
@click.option(
405408
"-r", "--raw", is_flag=True, help="Print raw cryptographic data and exit."
406409
)
@@ -416,6 +419,7 @@ def authenticate(
416419
hex_challenge: str | None,
417420
p256_root: t.BinaryIO | None,
418421
ed25519_root: t.BinaryIO | None,
422+
mldsa44_root: t.BinaryIO | None,
419423
raw: bool | None,
420424
offline: bool | None,
421425
) -> None:
@@ -440,6 +444,8 @@ def authenticate(
440444
_print_auth_data(msg.optiga_signature, msg.optiga_certificates)
441445
if msg.tropic_signature is not None:
442446
_print_auth_data(msg.tropic_signature, msg.tropic_certificates)
447+
if msg.mcu_signature is not None:
448+
_print_auth_data(msg.mcu_signature, msg.mcu_certificates)
443449
return
444450

445451
if p256_root is not None:
@@ -452,6 +458,11 @@ def authenticate(
452458
else:
453459
ed25519_root_bytes = None
454460

461+
if mldsa44_root is not None:
462+
mldsa44_root_bytes = mldsa44_root.read()
463+
else:
464+
mldsa44_root_bytes = None
465+
455466
class ColoredFormatter(logging.Formatter):
456467
LEVELS = {
457468
logging.ERROR: click.style("ERROR", fg="red"),
@@ -495,6 +506,7 @@ def format(self, record: logging.LogRecord) -> str:
495506
challenge,
496507
p256_root_pubkey=p256_root_bytes,
497508
ed25519_root_pubkey=ed25519_root_bytes,
509+
mldsa44_root_pubkey=mldsa44_root_bytes,
498510
allowlist=allowlist,
499511
)
500512
except authentication.DeviceNotAuthentic:

0 commit comments

Comments
 (0)