Skip to content

Commit 0091a8e

Browse files
committed
trezor: implement firmware update
1 parent dba7d33 commit 0091a8e

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

hwilib/devices/trezor.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from ..hwwclient import HardwareWalletClient
44
from ..errors import ActionCanceledError, BadArgumentError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, DeviceConnectionError, DEVICE_NOT_INITIALIZED, DeviceNotReadyError, UnavailableActionError, common_err_msgs, handle_errors
5+
from .trezorlib import firmware
56
from .trezorlib.client import TrezorClient as Trezor
67
from .trezorlib.debuglink import TrezorClientDebugLink
78
from .trezorlib.exceptions import Cancelled
@@ -86,6 +87,40 @@ def interactive_get_pin(self, code=None):
8687
else:
8788
return pin
8889

90+
ALLOWED_FIRMWARE_FORMATS = {
91+
1: (firmware.FirmwareFormat.TREZOR_ONE, firmware.FirmwareFormat.TREZOR_ONE_V2),
92+
2: (firmware.FirmwareFormat.TREZOR_T,),
93+
}
94+
95+
def _print_version(version):
96+
vstr = "Firmware version {major}.{minor}.{patch} build {build}".format(**version)
97+
print(vstr, file=sys.stderr)
98+
99+
def validate_firmware(version, fw, expected_fingerprint=None):
100+
if version == firmware.FirmwareFormat.TREZOR_ONE:
101+
if fw.embedded_onev2:
102+
print("Trezor One firmware with embedded v2 image (1.8.0 or later)", file=sys.stderr)
103+
_print_version(fw.embedded_onev2.firmware_header.version)
104+
else:
105+
print("Trezor One firmware image.", file=sys.stderr)
106+
elif version == firmware.FirmwareFormat.TREZOR_ONE_V2:
107+
print("Trezor One v2 firmware (1.8.0 or later)", file=sys.stderr)
108+
_print_version(fw.firmware_header.version)
109+
elif version == firmware.FirmwareFormat.TREZOR_T:
110+
print("Trezor T firmware image.", file=sys.stderr)
111+
vendor = fw.vendor_header.vendor_string
112+
vendor_version = "{major}.{minor}".format(**fw.vendor_header.version)
113+
print("Vendor header from {}, version {}".format(vendor, vendor_version), file=sys.stderr)
114+
_print_version(fw.firmware_header.version)
115+
116+
firmware.validate(version, fw, allow_unsigned=False)
117+
print("Signatures are valid.", file=sys.stderr)
118+
119+
fingerprint = firmware.digest(version, fw).hex()
120+
print("Firmware fingerprint: {}".format(fingerprint), file=sys.stderr)
121+
if expected_fingerprint and fingerprint != expected_fingerprint:
122+
raise BadArgumentError('Expected firmware fingerprint {} does not match computed fingerprint {}.'.format(expected_fingerprint, fingerprint))
123+
89124
# This class extends the HardwareWalletClient for Trezor specific things
90125
class TrezorClient(HardwareWalletClient):
91126

@@ -431,7 +466,37 @@ def send_pin(self, pin):
431466

432467
# Verify firmware file then load it onto device
433468
def update_firmware(self, file):
434-
raise NotImplementedError('The {} does not implement this method yet'.format(self.type))
469+
self.client.init_device(True)
470+
if not self.client.features.bootloader_mode:
471+
raise DeviceConnectionError('Device needs to be in bootloader mode')
472+
473+
bootloader_onev2 = self.client.features.major_version == 1 and self.client.features.minor_version >= 8
474+
475+
data = open(file, "rb").read()
476+
version, fw = firmware.parse(data)
477+
validate_firmware(version, fw)
478+
479+
if bootloader_onev2 and version == firmware.FirmwareFormat.TREZOR_ONE and not fw.embedded_onev2:
480+
raise BadArgumentError('Firmware is too old for your device')
481+
elif not bootloader_onev2 and version == firmware.FirmwareFormat.TREZOR_ONE_V2:
482+
raise BadArgumentError('You need to upgrade to bootloader 1.8.0 first.')
483+
484+
if self.client.features.major_version not in ALLOWED_FIRMWARE_FORMATS:
485+
raise BadArgumentError('Device has unknown version, unable to upgrade firmware')
486+
elif version not in ALLOWED_FIRMWARE_FORMATS[self.client.features.major_version]:
487+
raise BadArgumentError('Firmware does not match your device')
488+
489+
# special handling for embedded-OneV2 format:
490+
# for bootloader < 1.8, keep the embedding
491+
# for bootloader 1.8.0 and up, strip the old OneV1 header
492+
if bootloader_onev2 and data[:4] == b"TRZR" and data[256:256 + 4] == b"TRZF":
493+
data = data[256:]
494+
495+
if self.client.features.major_version == 1 and self.client.features.firmware_present is not False:
496+
# Trezor One does not send ButtonRequest
497+
print("Please confirm the action on your device", file=sys.stderr)
498+
firmware.update(self.client, data)
499+
return {'success': True}
435500

436501
def enumerate(password=''):
437502
results = []
@@ -448,7 +513,7 @@ def enumerate(password=''):
448513
client = None
449514
with handle_errors(common_err_msgs["enumerate"], d_data):
450515
client = TrezorClient(d_data['path'], password)
451-
client.client.init_device()
516+
client.client.init_device(True)
452517
if 'trezor' not in client.client.features.vendor:
453518
continue
454519

hwilib/devices/trezorlib/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ def call(self, msg):
177177
return resp
178178

179179
@tools.session
180-
def init_device(self):
181-
resp = self.call_raw(messages.GetFeatures())
180+
def init_device(self, initialize=False):
181+
resp = messages.Failure()
182+
if not initialize:
183+
resp = self.call_raw(messages.GetFeatures())
182184
# If GetFeatures fails, try initializing and clearing inconsistent state on the device
183185
if isinstance(resp, messages.Failure):
184186
resp = self.call_raw(messages.Initialize())

0 commit comments

Comments
 (0)