From b37afe01997c0a9ce6f7da34e78dce46e56ebe9e Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 19 Dec 2022 16:27:51 +0100 Subject: [PATCH] refactor(python/firmware): introduce legacy v3 signing detection related, change verify() method to accept dev_keys bool and select the appropriate keys, as opposed to caller needing to figure out the keys --- python/.changelog.d/2701.added | 1 + python/.changelog.d/2701.incompatible | 1 + python/src/trezorlib/firmware/__init__.py | 2 +- python/src/trezorlib/firmware/core.py | 8 +- python/src/trezorlib/firmware/legacy.py | 96 +++++++++++++++++++---- python/src/trezorlib/firmware/vendor.py | 14 +++- 6 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 python/.changelog.d/2701.added create mode 100644 python/.changelog.d/2701.incompatible diff --git a/python/.changelog.d/2701.added b/python/.changelog.d/2701.added new file mode 100644 index 0000000000..dcead09431 --- /dev/null +++ b/python/.changelog.d/2701.added @@ -0,0 +1 @@ +Add support for v3-style Trezor One signatures. diff --git a/python/.changelog.d/2701.incompatible b/python/.changelog.d/2701.incompatible new file mode 100644 index 0000000000..136848ec18 --- /dev/null +++ b/python/.changelog.d/2701.incompatible @@ -0,0 +1 @@ +Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. diff --git a/python/src/trezorlib/firmware/__init__.py b/python/src/trezorlib/firmware/__init__.py index 1979d843ec..031b8132e5 100644 --- a/python/src/trezorlib/firmware/__init__.py +++ b/python/src/trezorlib/firmware/__init__.py @@ -47,7 +47,7 @@ if t.TYPE_CHECKING: def parse(cls: t.Type[T], data: bytes) -> T: ... - def verify(self, public_keys: t.Sequence[bytes] = ()) -> None: + def verify(self, dev_keys: bool = False) -> None: ... def digest(self) -> bytes: diff --git a/python/src/trezorlib/firmware/core.py b/python/src/trezorlib/firmware/core.py index b8c8533154..934dfb2851 100644 --- a/python/src/trezorlib/firmware/core.py +++ b/python/src/trezorlib/firmware/core.py @@ -185,9 +185,11 @@ class VendorFirmware(Struct): def digest(self) -> bytes: return self.firmware.digest() - def verify(self, _public_keys: t.Sequence[bytes] = ()) -> None: - if _public_keys: - raise ValueError("Cannot supply custom keys for vendor firmware.") + def verify(self, dev_keys: bool = False) -> None: + if dev_keys: + raise ValueError( + "Cannot select dev keys for a vendor firmware; use development vendor header instead." + ) self.firmware.validate_code_hashes() diff --git a/python/src/trezorlib/firmware/legacy.py b/python/src/trezorlib/firmware/legacy.py index 6c7ed4f0ef..1f6036d9c9 100644 --- a/python/src/trezorlib/firmware/legacy.py +++ b/python/src/trezorlib/firmware/legacy.py @@ -22,7 +22,7 @@ import construct as c import ecdsa from construct_classes import Struct, subcon -from . import consts, util +from . import consts, models, util from .core import FirmwareImage __all__ = [ @@ -32,23 +32,32 @@ __all__ = [ ] +ZERO_SIG = b"\x00" * 64 + + def check_sig_v1( digest: bytes, key_indexes: t.Sequence[int], signatures: t.Sequence[bytes], + sigs_required: int, public_keys: t.Sequence[bytes], ) -> None: """Validate signatures of `digest` using the Trezor One V1 method.""" - distinct_indexes = set(i for i in key_indexes if i != 0) + distinct_indexes = set(i for i in key_indexes[:sigs_required] if i != 0) if not distinct_indexes: raise util.Unsigned - if len(distinct_indexes) < len(key_indexes): + if len(distinct_indexes) != sigs_required: raise util.InvalidSignatureError( - f"Not enough distinct signatures (found {len(distinct_indexes)}, need {len(key_indexes)})" + f"Not enough distinct signatures (found {len(distinct_indexes)}, need {sigs_required})" ) - for i in range(len(key_indexes)): + if any(k != 0 for k in key_indexes[sigs_required:]) or any( + sig != ZERO_SIG for sig in signatures[sigs_required:] + ): + raise util.InvalidSignatureError("Too many signatures") + + for i in range(sigs_required): key_idx = key_indexes[i] - 1 signature = signatures[i] @@ -56,14 +65,36 @@ def check_sig_v1( # unknown pubkey raise util.InvalidSignatureError(f"Unknown key in slot {i}") - pubkey = public_keys[key_idx][1:] - verify = ecdsa.VerifyingKey.from_string(pubkey, curve=ecdsa.curves.SECP256k1) + verify = ecdsa.VerifyingKey.from_string( + public_keys[key_idx], + curve=ecdsa.curves.SECP256k1, + hashfunc=hashlib.sha256, + ) try: verify.verify_digest(signature, digest) except ecdsa.BadSignatureError as e: raise util.InvalidSignatureError(f"Invalid signature in slot {i}") from e +def check_sig_signmessage( + digest: bytes, + key_indexes: t.Sequence[int], + signatures: t.Sequence[bytes], + sigs_required: int, + public_keys: t.Sequence[bytes], +) -> None: + """Validate signatures of `digest` using the Trezor One SignMessage method.""" + btc_digest = hashlib.sha256(b"\x18Bitcoin Signed Message:\n\x20" + digest).digest() + final_digest = hashlib.sha256(btc_digest).digest() + check_sig_v1( + final_digest, + key_indexes, + signatures, + sigs_required, + public_keys, + ) + + class LegacyV2Firmware(FirmwareImage): """Firmware image in the format used by Trezor One 1.8.0 and newer.""" @@ -73,17 +104,49 @@ class LegacyV2Firmware(FirmwareImage): padding_byte=b"\xff", ) - def verify( - self, public_keys: t.Sequence[bytes] = consts.V1_BOOTLOADER_KEYS - ) -> None: + V3_FIRST_VERSION = (1, 12, 0) + + def verify_v2(self, dev_keys: bool) -> None: + if not dev_keys: + public_keys = models.TREZOR_ONE_V1V2.firmware_keys + else: + public_keys = models.TREZOR_ONE_V1V2_DEV.firmware_keys + self.validate_code_hashes() check_sig_v1( self.digest(), self.header.v1_key_indexes, self.header.v1_signatures, + models.TREZOR_ONE_V1V2.firmware_sigs_needed, public_keys, ) + def verify_v3(self, dev_keys: bool) -> None: + if not dev_keys: + model_keys = models.TREZOR_ONE_V3 + else: + model_keys = models.TREZOR_ONE_V3_DEV + + self.validate_code_hashes() + check_sig_signmessage( + self.digest(), + self.header.v1_key_indexes, + self.header.v1_signatures, + model_keys.firmware_sigs_needed, + model_keys.firmware_keys, + ) + + def verify(self, dev_keys: bool = False) -> None: + if self.header.version >= self.V3_FIRST_VERSION: + try: + self.verify_v3(dev_keys) + except util.InvalidSignatureError: + pass + else: + return + + self.verify_v2(dev_keys) + def verify_unsigned(self) -> None: self.validate_code_hashes() if any(i != 0 for i in self.header.v1_key_indexes): @@ -126,15 +189,18 @@ class LegacyFirmware(Struct): def digest(self) -> bytes: return hashlib.sha256(self.code).digest() - def verify( - self, public_keys: t.Sequence[bytes] = consts.V1_BOOTLOADER_KEYS - ) -> None: + def verify(self, dev_keys: bool = False) -> None: + if not dev_keys: + model_keys = models.TREZOR_ONE_V1V2 + else: + model_keys = models.TREZOR_ONE_V1V2_DEV check_sig_v1( self.digest(), self.key_indexes, self.signatures, - public_keys, + model_keys.firmware_sigs_needed, + model_keys.firmware_keys, ) if self.embedded_v2: - self.embedded_v2.verify(consts.V1_BOOTLOADER_KEYS) + self.embedded_v2.verify() diff --git a/python/src/trezorlib/firmware/vendor.py b/python/src/trezorlib/firmware/vendor.py index 14696d3cc6..154859e9e6 100644 --- a/python/src/trezorlib/firmware/vendor.py +++ b/python/src/trezorlib/firmware/vendor.py @@ -24,7 +24,8 @@ from construct_classes import Struct, subcon from .. import cosi from ..toif import ToifStruct from ..tools import TupleAdapter -from . import consts, util +from . import util +from .models import TREZOR_T, TREZOR_T_DEV __all__ = [ "VendorTrust", @@ -125,14 +126,19 @@ class VendorHeader(Struct): h.update(b"\x00" * 32) return h.digest() - def verify(self, pubkeys: t.Sequence[bytes] = consts.V2_BOOTLOADER_KEYS) -> None: + def verify(self, dev_keys: bool = False) -> None: digest = self.digest() + if not dev_keys: + public_keys = TREZOR_T.bootloader_keys + else: + public_keys = TREZOR_T_DEV.bootloader_keys + # TODO: add model awareness try: cosi.verify( self.signature, digest, - consts.V2_SIGS_REQUIRED, - pubkeys, + TREZOR_T.bootloader_sigs_needed, + public_keys, self.sigmask, ) except Exception: