|
|
|
@ -16,6 +16,7 @@
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
from typing import (
|
|
|
|
|
TYPE_CHECKING,
|
|
|
|
|
Any,
|
|
|
|
@ -32,7 +33,7 @@ from urllib.parse import urlparse
|
|
|
|
|
import click
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
from .. import exceptions, firmware, messages, models
|
|
|
|
|
from .. import device, exceptions, firmware, messages, models
|
|
|
|
|
from ..firmware import models as fw_models
|
|
|
|
|
from ..models import TrezorModel
|
|
|
|
|
from . import ChoiceType, with_client
|
|
|
|
@ -460,6 +461,43 @@ def upload_firmware_into_device(
|
|
|
|
|
sys.exit(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_strict_update(client: "TrezorClient", firmware_data: bytes) -> bool:
|
|
|
|
|
"""Check if the firmware is from the same vendor and the
|
|
|
|
|
firmware is newer than the currently installed firmware.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
fw = firmware.parse(firmware_data)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
click.echo(e)
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
|
|
|
|
|
if not isinstance(fw, firmware.VendorFirmware):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
f = client.features
|
|
|
|
|
cur_version = (f.major_version, f.minor_version, f.patch_version, 0)
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
fw.vendor_header.text == f.fw_vendor
|
|
|
|
|
and fw.firmware.header.version > cur_version
|
|
|
|
|
and fw.vendor_header.trust.is_full_trust()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_firmware_header_size(firmware_data: bytes) -> int:
|
|
|
|
|
"""Returns size of vendor and image headers"""
|
|
|
|
|
try:
|
|
|
|
|
fw = firmware.parse(firmware_data)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
click.echo(e)
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
|
|
|
|
|
if isinstance(fw, firmware.VendorFirmware):
|
|
|
|
|
return fw.firmware.header.header_len + fw.vendor_header.header_len
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@click.group(name="firmware")
|
|
|
|
|
def cli() -> None:
|
|
|
|
|
"""Firmware commands."""
|
|
|
|
@ -581,9 +619,9 @@ def download(
|
|
|
|
|
@click.option("--raw", is_flag=True, help="Push raw firmware data to Trezor")
|
|
|
|
|
@click.option("--fingerprint", help="Expected firmware fingerprint in hex")
|
|
|
|
|
# fmt: on
|
|
|
|
|
@with_client
|
|
|
|
|
@click.pass_obj
|
|
|
|
|
def update(
|
|
|
|
|
client: "TrezorClient",
|
|
|
|
|
obj: "TrezorConnection",
|
|
|
|
|
filename: Optional[BinaryIO],
|
|
|
|
|
url: Optional[str],
|
|
|
|
|
version: Optional[str],
|
|
|
|
@ -596,8 +634,6 @@ def update(
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Upload new firmware to device.
|
|
|
|
|
|
|
|
|
|
Device must be in bootloader mode.
|
|
|
|
|
|
|
|
|
|
You can specify a filename or URL from which the firmware can be downloaded.
|
|
|
|
|
You can also explicitly specify a firmware version that you want.
|
|
|
|
|
Otherwise, trezorctl will attempt to find latest available version
|
|
|
|
@ -607,43 +643,66 @@ def update(
|
|
|
|
|
against downloaded firmware fingerprint. Otherwise fingerprint is checked
|
|
|
|
|
against data.trezor.io information, if available.
|
|
|
|
|
"""
|
|
|
|
|
if sum(bool(x) for x in (filename, url, version)) > 1:
|
|
|
|
|
click.echo("You can use only one of: filename, url, version.")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if not dry_run and not client.features.bootloader_mode:
|
|
|
|
|
click.echo("Please switch your device to bootloader mode.")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
with obj.client_context() as client:
|
|
|
|
|
if sum(bool(x) for x in (filename, url, version)) > 1:
|
|
|
|
|
click.echo("You can use only one of: filename, url, version.")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if filename:
|
|
|
|
|
firmware_data = filename.read()
|
|
|
|
|
else:
|
|
|
|
|
if not url:
|
|
|
|
|
url, fp = find_best_firmware_version(
|
|
|
|
|
client=client, version=version, beta=beta, bitcoin_only=bitcoin_only
|
|
|
|
|
if filename:
|
|
|
|
|
firmware_data = filename.read()
|
|
|
|
|
else:
|
|
|
|
|
if not url:
|
|
|
|
|
url, fp = find_best_firmware_version(
|
|
|
|
|
client=client, version=version, beta=beta, bitcoin_only=bitcoin_only
|
|
|
|
|
)
|
|
|
|
|
if not fingerprint:
|
|
|
|
|
fingerprint = fp
|
|
|
|
|
|
|
|
|
|
firmware_data = download_firmware_data(url)
|
|
|
|
|
|
|
|
|
|
if not raw and not skip_check:
|
|
|
|
|
validate_firmware(
|
|
|
|
|
firmware_data=firmware_data,
|
|
|
|
|
fingerprint=fingerprint,
|
|
|
|
|
bootloader_onev2=_is_bootloader_onev2(client),
|
|
|
|
|
model=client.model,
|
|
|
|
|
)
|
|
|
|
|
if not fingerprint:
|
|
|
|
|
fingerprint = fp
|
|
|
|
|
|
|
|
|
|
firmware_data = download_firmware_data(url)
|
|
|
|
|
if not raw:
|
|
|
|
|
firmware_data = extract_embedded_fw(
|
|
|
|
|
firmware_data=firmware_data,
|
|
|
|
|
bootloader_onev2=_is_bootloader_onev2(client),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if dry_run:
|
|
|
|
|
click.echo("Dry run. Not uploading firmware to device.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not client.features.bootloader_mode:
|
|
|
|
|
if _is_strict_update(client, firmware_data):
|
|
|
|
|
header_size = _get_firmware_header_size(firmware_data)
|
|
|
|
|
device.reboot_to_bootloader(
|
|
|
|
|
client,
|
|
|
|
|
boot_command=messages.BootCommand.INSTALL_UPGRADE,
|
|
|
|
|
firmware_header=firmware_data[:header_size],
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
device.reboot_to_bootloader(client)
|
|
|
|
|
|
|
|
|
|
if not raw and not skip_check:
|
|
|
|
|
validate_firmware(
|
|
|
|
|
firmware_data=firmware_data,
|
|
|
|
|
fingerprint=fingerprint,
|
|
|
|
|
bootloader_onev2=_is_bootloader_onev2(client),
|
|
|
|
|
model=client.model,
|
|
|
|
|
)
|
|
|
|
|
click.echo("Waiting for bootloader...")
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
try:
|
|
|
|
|
obj.get_transport()
|
|
|
|
|
break
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
if not raw:
|
|
|
|
|
firmware_data = extract_embedded_fw(
|
|
|
|
|
firmware_data=firmware_data,
|
|
|
|
|
bootloader_onev2=_is_bootloader_onev2(client),
|
|
|
|
|
)
|
|
|
|
|
with obj.client_context() as client:
|
|
|
|
|
if not client.features.bootloader_mode:
|
|
|
|
|
click.echo("Please switch your device to bootloader mode.")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if dry_run:
|
|
|
|
|
click.echo("Dry run. Not uploading firmware to device.")
|
|
|
|
|
else:
|
|
|
|
|
upload_firmware_into_device(client=client, firmware_data=firmware_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|