You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/python/tests/test_firmware.py

154 lines
4.1 KiB

from pathlib import Path
import construct
import pytest
import requests
from trezorlib import firmware
from trezorlib.firmware import (
VendorFirmware,
LegacyFirmware,
LegacyV2Firmware,
VendorHeader,
)
CORE_FW_VERSION = "2.4.2"
CORE_FW_FINGERPRINT = "54ccf155510b5292bd17ed748409d0d135112e24e62eb74184639460beecb213"
LEGACY_FW_VERSION = "1.10.3"
LEGACY_FW_FINGERPRINT = (
"bf0cc936a9afbf0a4ae7b727a2817fb69fba432d7230a0ff7b79b4a73b845197"
)
CORE_FW = f"https://data.trezor.io/firmware/2/trezor-{CORE_FW_VERSION}.bin"
LEGACY_FW = f"https://data.trezor.io/firmware/1/trezor-{LEGACY_FW_VERSION}.bin"
HERE = Path(__file__).parent
VENDOR_HEADER = (
HERE.parent.parent
/ "core"
/ "embed"
/ "vendorheader"
/ "vendorheader_satoshilabs_signed_prod.bin"
)
def _fetch(url: str, version: str) -> bytes:
path = HERE / f"trezor-{version}.bin"
if not path.exists():
r = requests.get(url)
r.raise_for_status()
path.write_bytes(r.content)
return path.read_bytes()
@pytest.fixture()
def legacy_fw() -> bytes:
return _fetch(LEGACY_FW, LEGACY_FW_VERSION)
@pytest.fixture()
def core_fw() -> bytes:
return _fetch(CORE_FW, CORE_FW_VERSION)
def test_core_basic(core_fw: bytes) -> None:
fw = VendorFirmware.parse(core_fw)
fw.verify()
assert fw.digest().hex() == CORE_FW_FINGERPRINT
version_str = ".".join(str(x) for x in fw.firmware.header.version)
assert version_str.startswith(CORE_FW_VERSION)
assert fw.vendor_header.text == "SatoshiLabs"
assert fw.build() == core_fw
def test_vendor_header(core_fw: bytes) -> None:
fw = VendorFirmware.parse(core_fw)
vh_data = fw.vendor_header.build()
assert vh_data in core_fw
assert vh_data == VENDOR_HEADER.read_bytes()
vh = VendorHeader.parse(vh_data)
assert vh == fw.vendor_header
vh.verify()
with pytest.raises(construct.ConstructError):
VendorFirmware.parse(vh_data)
def test_core_code_hashes(core_fw: bytes) -> None:
fw = VendorFirmware.parse(core_fw)
fw.firmware.header.hashes = []
assert fw.digest().hex() == CORE_FW_FINGERPRINT
def test_legacy_basic(legacy_fw: bytes) -> None:
fw = LegacyFirmware.parse(legacy_fw)
fw.verify()
assert fw.digest().hex() == LEGACY_FW_FINGERPRINT
assert fw.build() == legacy_fw
def test_unsigned(legacy_fw: bytes) -> None:
legacy = LegacyFirmware.parse(legacy_fw)
legacy.verify()
legacy.key_indexes = [0, 0, 0]
legacy.signatures = [b"", b"", b""]
with pytest.raises(firmware.Unsigned):
legacy.verify()
assert legacy.embedded_v2 is not None
legacy.embedded_v2.verify()
legacy.embedded_v2.header.v1_key_indexes = [0, 0, 0]
legacy.embedded_v2.header.v1_signatures = [b"", b"", b""]
with pytest.raises(firmware.Unsigned):
legacy.embedded_v2.verify()
def test_disallow_unsigned(core_fw: bytes) -> None:
core = VendorFirmware.parse(core_fw)
core.firmware.header.sigmask = 0
core.firmware.header.signature = b""
with pytest.raises(firmware.InvalidSignatureError):
core.verify()
def test_embedded_v2(legacy_fw: bytes) -> None:
legacy = LegacyFirmware.parse(legacy_fw)
assert legacy.embedded_v2 is not None
legacy.embedded_v2.verify()
embedded_data = legacy.embedded_v2.build()
cutoff_data = legacy_fw[256:]
assert cutoff_data == embedded_data
embedded = LegacyV2Firmware.parse(cutoff_data)
assert embedded == legacy.embedded_v2
def test_integrity_legacy(legacy_fw: bytes) -> None:
legacy = LegacyFirmware.parse(legacy_fw)
legacy.verify()
modified_data = bytearray(legacy_fw)
modified_data[-1] ^= 0x01
modified = LegacyFirmware.parse(modified_data)
assert modified.digest() != legacy.digest()
with pytest.raises(firmware.InvalidSignatureError):
modified.verify()
def test_integrity_core(core_fw: bytes) -> None:
core = VendorFirmware.parse(core_fw)
core.verify()
modified_data = bytearray(core_fw)
modified_data[-1] ^= 0x01
modified = VendorFirmware.parse(modified_data)
assert modified.digest() != core.digest()
with pytest.raises(firmware.FirmwareIntegrityError):
modified.verify()