mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-21 23:18:13 +00:00
feat(python): introduce interaction-less upgrade
This commit is contained in:
parent
ba83a7e644
commit
523e50db49
1
python/.changelog.d/2919.added.1
Normal file
1
python/.changelog.d/2919.added.1
Normal file
@ -0,0 +1 @@
|
||||
Support interaction-less upgrade
|
1
python/.changelog.d/2919.added.2
Normal file
1
python/.changelog.d/2919.added.2
Normal file
@ -0,0 +1 @@
|
||||
trezorctl: Automatically go to bootloader when upgrading firmware
|
@ -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)
|
||||
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 not dry_run and not client.features.bootloader_mode:
|
||||
click.echo("Please switch your device to bootloader mode.")
|
||||
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 not fingerprint:
|
||||
fingerprint = fp
|
||||
|
||||
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
|
||||
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 not raw and not skip_check:
|
||||
validate_firmware(
|
||||
firmware_data=firmware_data,
|
||||
fingerprint=fingerprint,
|
||||
bootloader_onev2=_is_bootloader_onev2(client),
|
||||
model=client.model,
|
||||
)
|
||||
if dry_run:
|
||||
click.echo("Dry run. Not uploading firmware to device.")
|
||||
return
|
||||
|
||||
if not raw:
|
||||
firmware_data = extract_embedded_fw(
|
||||
firmware_data=firmware_data,
|
||||
bootloader_onev2=_is_bootloader_onev2(client),
|
||||
)
|
||||
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)
|
||||
|
||||
click.echo("Waiting for bootloader...")
|
||||
while True:
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
obj.get_transport()
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -70,6 +70,14 @@ class VendorTrust(Struct):
|
||||
2,
|
||||
)
|
||||
|
||||
def is_full_trust(self) -> bool:
|
||||
return (
|
||||
not self.show_vendor_string
|
||||
and not self.require_user_click
|
||||
and not self.red_background
|
||||
and self.delay == 0
|
||||
)
|
||||
|
||||
|
||||
class VendorHeader(Struct):
|
||||
header_len: int
|
||||
|
@ -485,6 +485,11 @@ class WordRequestType(IntEnum):
|
||||
Matrix6 = 2
|
||||
|
||||
|
||||
class BootCommand(IntEnum):
|
||||
STOP_AND_WAIT = 0
|
||||
INSTALL_UPGRADE = 1
|
||||
|
||||
|
||||
class DebugSwipeDirection(IntEnum):
|
||||
UP = 0
|
||||
DOWN = 1
|
||||
@ -3746,6 +3751,19 @@ class CancelAuthorization(protobuf.MessageType):
|
||||
|
||||
class RebootToBootloader(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 87
|
||||
FIELDS = {
|
||||
1: protobuf.Field("boot_command", "BootCommand", repeated=False, required=False, default=BootCommand.STOP_AND_WAIT),
|
||||
2: protobuf.Field("firmware_header", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
boot_command: Optional["BootCommand"] = BootCommand.STOP_AND_WAIT,
|
||||
firmware_header: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.boot_command = boot_command
|
||||
self.firmware_header = firmware_header
|
||||
|
||||
|
||||
class GetNonce(protobuf.MessageType):
|
||||
|
Loading…
Reference in New Issue
Block a user