Skip to content

Commit ae60f04

Browse files
committed
trezor: implement firmware update
1 parent 5bbd22a commit ae60f04

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

@@ -436,7 +471,37 @@ def send_pin(self, pin):
436471

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

441506
def enumerate(password=''):
442507
results = []
@@ -453,7 +518,7 @@ def enumerate(password=''):
453518
client = None
454519
with handle_errors(common_err_msgs["enumerate"], d_data):
455520
client = TrezorClient(d_data['path'], password)
456-
client.client.init_device()
521+
client.client.init_device(True)
457522
if 'trezor' not in client.client.features.vendor:
458523
continue
459524

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)