diff --git a/core/.changelog.d/3048.added b/core/.changelog.d/3048.added new file mode 100644 index 0000000000..9ca067e4da --- /dev/null +++ b/core/.changelog.d/3048.added @@ -0,0 +1 @@ +Added hw model field to all vendor headers. diff --git a/core/embed/vendorheader/vendor_prodtest.json b/core/embed/vendorheader/vendor_prodtest.json index 81312d57c3..ca4d021e13 100644 --- a/core/embed/vendorheader/vendor_prodtest.json +++ b/core/embed/vendorheader/vendor_prodtest.json @@ -1,6 +1,7 @@ { "header_len": 4608, "text": "UNSAFE, FACTORY TEST ONLY", + "hw_model": null, "expiry": 0, "version": [0, 0], "sig_m": 2, diff --git a/core/embed/vendorheader/vendor_qa_DO_NOT_SIGN.json b/core/embed/vendorheader/vendor_qa_DO_NOT_SIGN.json index 570919630b..cbd85714be 100644 --- a/core/embed/vendorheader/vendor_qa_DO_NOT_SIGN.json +++ b/core/embed/vendorheader/vendor_qa_DO_NOT_SIGN.json @@ -1,6 +1,7 @@ { "header_len": 4608, "text": "QA ONLY, DO NOT USE!", + "hw_model": null, "expiry": 0, "version": [0, 0], "sig_m": 2, diff --git a/core/embed/vendorheader/vendor_satoshilabs.json b/core/embed/vendorheader/vendor_satoshilabs.json index ffb4839b36..9e40a08d69 100644 --- a/core/embed/vendorheader/vendor_satoshilabs.json +++ b/core/embed/vendorheader/vendor_satoshilabs.json @@ -1,6 +1,7 @@ { "header_len": 4608, "text": "SatoshiLabs", + "hw_model": null, "expiry": 0, "version": [0, 1], "sig_m": 2, diff --git a/core/embed/vendorheader/vendor_unsafe.json b/core/embed/vendorheader/vendor_unsafe.json index 9af2b1b964..54e8ce9db4 100644 --- a/core/embed/vendorheader/vendor_unsafe.json +++ b/core/embed/vendorheader/vendor_unsafe.json @@ -1,6 +1,7 @@ { "header_len": 4608, "text": "UNSAFE, DO NOT USE!", + "hw_model": null, "expiry": 0, "version": [0, 1], "sig_m": 2, diff --git a/core/tools/build_vendorheader b/core/tools/build_vendorheader index 17c28cb2e6..f71f69b7ff 100755 --- a/core/tools/build_vendorheader +++ b/core/tools/build_vendorheader @@ -27,6 +27,10 @@ def build_vendorheader(specfile, image, outfile): spec["image"] = toif.ToifStruct.parse(image.read()) spec["sigmask"] = 0 spec["signature"] = b"\x00" * 64 + if spec["hw_model"] is None: + spec["hw_model"] = b"\x00\x00\x00\x00" + else: + spec["hw_model"] = spec["hw_model"].encode("ascii") min_length = minimum_header_len(spec) if "header_len" not in spec: diff --git a/python/.changelog.d/3048.added b/python/.changelog.d/3048.added new file mode 100644 index 0000000000..9c2b9fffb3 --- /dev/null +++ b/python/.changelog.d/3048.added @@ -0,0 +1 @@ +Recognize hw model field in vendor headers. diff --git a/python/src/trezorlib/_internal/firmware_headers.py b/python/src/trezorlib/_internal/firmware_headers.py index c16713ba5f..b81884796f 100644 --- a/python/src/trezorlib/_internal/firmware_headers.py +++ b/python/src/trezorlib/_internal/firmware_headers.py @@ -264,6 +264,7 @@ class LegacySignedImage(SignableImageProto, Protocol): class CosiSignatureHeaderProto(Protocol): + hw_model: t.Union[fw_models.Model, bytes] signature: bytes sigmask: int @@ -280,6 +281,11 @@ class CosiSignedMixin: def get_header(self) -> CosiSignatureHeaderProto: raise NotImplementedError + def get_model_keys(self, dev_keys: bool) -> fw_models.ModelKeys: + hw_model = self.get_header().hw_model + model = fw_models.Model.from_hw_model(hw_model) + return model.model_keys(dev_keys) + class VendorHeader(firmware.VendorHeader, CosiSignedMixin): NAME: t.ClassVar[str] = "vendorheader" @@ -318,10 +324,7 @@ class VendorHeader(firmware.VendorHeader, CosiSignedMixin): return self._format(terse=False) def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: - if not dev_keys: - return fw_models.TREZOR_T.bootloader_keys - else: - return fw_models.TREZOR_T_DEV.bootloader_keys + return self.get_model_keys(dev_keys).bootloader_keys class VendorFirmware(firmware.VendorFirmware, CosiSignedMixin): @@ -362,18 +365,6 @@ class BootloaderImage(firmware.FirmwareImage, CosiSignedMixin): NAME: t.ClassVar[str] = "bootloader" DEV_KEYS = _make_dev_keys(b"\x41", b"\x42") - def get_model(self) -> fw_models.Model: - if isinstance(self.header.hw_model, fw_models.Model): - return self.header.hw_model - return fw_models.Model.T - - def get_model_keys(self, dev_keys: bool) -> fw_models.ModelKeys: - model = self.get_model() - if dev_keys: - return fw_models.MODEL_MAP_DEV[model] - else: - return fw_models.MODEL_MAP[model] - def get_header(self) -> CosiSignatureHeaderProto: return self.header diff --git a/python/src/trezorlib/firmware/core.py b/python/src/trezorlib/firmware/core.py index d073900b13..ee21abb9ab 100644 --- a/python/src/trezorlib/firmware/core.py +++ b/python/src/trezorlib/firmware/core.py @@ -48,7 +48,7 @@ class FirmwareHeader(Struct): code_length: int version: t.Tuple[int, int, int, int] fix_version: t.Tuple[int, int, int, int] - hw_model: Model + hw_model: t.Union[Model, bytes] hw_revision: int monotonic: int hashes: t.List[bytes] diff --git a/python/src/trezorlib/firmware/models.py b/python/src/trezorlib/firmware/models.py index 0c2fb901eb..21c0097664 100644 --- a/python/src/trezorlib/firmware/models.py +++ b/python/src/trezorlib/firmware/models.py @@ -18,12 +18,30 @@ import typing as t from dataclasses import dataclass from enum import Enum +if t.TYPE_CHECKING: + from typing_extensions import Self + class Model(Enum): ONE = b"T1B1" T = b"T2T1" R = b"T2B1" + @classmethod + def from_hw_model(cls, hw_model: t.Union["Self", bytes]) -> "Self": + if isinstance(hw_model, cls): + return hw_model + if hw_model == b"\x00\x00\x00\x00": + return cls.T + raise ValueError(f"Unknown hardware model: {hw_model}") + + def model_keys(self, dev_keys: bool = False) -> "ModelKeys": + if dev_keys: + model_map = MODEL_MAP_DEV + else: + model_map = MODEL_MAP + return model_map[self] + @dataclass class ModelKeys: diff --git a/python/src/trezorlib/firmware/vendor.py b/python/src/trezorlib/firmware/vendor.py index e7dc9e810b..a9fd0ebcec 100644 --- a/python/src/trezorlib/firmware/vendor.py +++ b/python/src/trezorlib/firmware/vendor.py @@ -23,9 +23,9 @@ from construct_classes import Struct, subcon from .. import cosi from ..toif import ToifStruct -from ..tools import TupleAdapter +from ..tools import EnumAdapter, TupleAdapter from . import util -from .models import TREZOR_T, TREZOR_T_DEV +from .models import Model __all__ = [ "VendorTrust", @@ -75,6 +75,7 @@ class VendorHeader(Struct): version: t.Tuple[int, int] sig_m: int # sig_n: int + hw_model: t.Union[Model, bytes] pubkeys: t.List[bytes] text: str image: t.Dict[str, t.Any] @@ -93,7 +94,8 @@ class VendorHeader(Struct): "sig_m" / c.Int8ul, "sig_n" / c.Rebuild(c.Int8ul, c.len_(c.this.pubkeys)), "trust" / VendorTrust.SUBCON, - "_reserved" / c.Padding(14), + "hw_model" / EnumAdapter(c.Bytes(4), Model), + "_reserved" / c.Padding(10), "pubkeys" / c.Bytes(32)[c.this.sig_n], "text" / c.Aligned(4, c.PascalString(c.Int8ul, "utf-8")), "image" / ToifStruct, @@ -128,19 +130,13 @@ class VendorHeader(Struct): def verify(self, dev_keys: bool = False) -> None: digest = self.digest() - if not dev_keys: - public_keys = TREZOR_T.bootloader_keys - sigs_needed = TREZOR_T.bootloader_sigs_needed - else: - public_keys = TREZOR_T_DEV.bootloader_keys - sigs_needed = TREZOR_T_DEV.bootloader_sigs_needed - # TODO: add model awareness + model_keys = Model.from_hw_model(self.hw_model).model_keys(dev_keys) try: cosi.verify( self.signature, digest, - sigs_needed, - public_keys, + model_keys.bootloader_sigs_needed, + model_keys.bootloader_keys, self.sigmask, ) except Exception: diff --git a/python/tests/test_firmware.py b/python/tests/test_firmware.py index 6df1c5a419..728be1966e 100644 --- a/python/tests/test_firmware.py +++ b/python/tests/test_firmware.py @@ -29,6 +29,7 @@ VENDOR_HEADER = ( / "core" / "embed" / "vendorheader" + / "T2T1" / "vendorheader_satoshilabs_signed_prod.bin" )