Skip to content

Commit 2b7596c

Browse files
committed
keepkey: update trezorlib for use with keepkey firmware updating too
1 parent 0091a8e commit 2b7596c

File tree

7 files changed

+76
-12
lines changed

7 files changed

+76
-12
lines changed

hwilib/devices/trezor.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ def interactive_get_pin(self, code=None):
8888
return pin
8989

9090
ALLOWED_FIRMWARE_FORMATS = {
91-
1: (firmware.FirmwareFormat.TREZOR_ONE, firmware.FirmwareFormat.TREZOR_ONE_V2),
92-
2: (firmware.FirmwareFormat.TREZOR_T,),
91+
1: (firmware.FirmwareFormat.TREZOR_ONE, firmware.FirmwareFormat.TREZOR_ONE_V2, firmware.FirmwareFormat.KEEPKEY),
92+
2: (firmware.FirmwareFormat.TREZOR_T, firmware.FirmwareFormat.KEEPKEY),
9393
}
9494

9595
def _print_version(version):
@@ -103,6 +103,8 @@ def validate_firmware(version, fw, expected_fingerprint=None):
103103
_print_version(fw.embedded_onev2.firmware_header.version)
104104
else:
105105
print("Trezor One firmware image.", file=sys.stderr)
106+
elif version == firmware.FirmwareFormat.TREZOR_ONE_V2:
107+
print('Keepkey firmware image', file=sys.stderr)
106108
elif version == firmware.FirmwareFormat.TREZOR_ONE_V2:
107109
print("Trezor One v2 firmware (1.8.0 or later)", file=sys.stderr)
108110
_print_version(fw.firmware_header.version)
@@ -495,7 +497,7 @@ def update_firmware(self, file):
495497
if self.client.features.major_version == 1 and self.client.features.firmware_present is not False:
496498
# Trezor One does not send ButtonRequest
497499
print("Please confirm the action on your device", file=sys.stderr)
498-
firmware.update(self.client, data)
500+
firmware.update(self.client, data, version)
499501
return {'success': True}
500502

501503
def enumerate(password=''):

hwilib/devices/trezorlib/firmware.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@
3737
4: "04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a",
3838
5: "047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45",
3939
}
40+
KEEPKEY_BOOTLOADER_KEYS = {
41+
1: "04a33cec36d6d011af09e0c498d17c3ba7ab907abfbb64caba16ad9077caacd3e198a32362c32d0ef0a7269259abbbcd8a688a0c8f54a6dbc405459566cd65141d",
42+
2: "04ab291f6bd33d0e3974f27e50070be933695a0fab7b8b3654e7c9dce74f7f98fd739b1ed86eb0be26f026e4dc6519fd2884955fa174f8a783fe455ac43f944c70",
43+
3: "04a9c29f4e053b35ffd3b9a37b073188b624c07c3a92622c131edf1a2b2c712216a8c06c9ddfdcaa39b81d9a86f0459480b0277eab0e30a34f1d26b326b8995a33",
44+
4: "04f228448eaf05171ccb68a04a0724ac586b846c54c5fd0a526f9d7c3396c98dd47aef6b2faf47b54ffa8c2861c54920ce6c2aa5607c496869023724db285495c6",
45+
5: "0418a90b536e9ffb0ec320293c33754af89b145475c4d921f818e2062c92b01be526047ccfa042b4711fb5603fe6bd7980693100b71ee766d86116a3694873f314",
46+
}
4047

4148
V2_BOOTLOADER_KEYS = [
4249
bytes.fromhex("c2c87a49c5a3460977fbb2ec9dfe60f06bd694db8244bd4981fe3b7a26307f3f"),
@@ -220,13 +227,29 @@ def _decode(self, obj, ctx, path):
220227
"embedded_onev2" / c.RestreamData(c.this.code, c.Optional(FirmwareOneV2)),
221228
)
222229

230+
231+
FirmwareKeepkey = c.Struct(
232+
"magic" / c.Const(b"KPKY"),
233+
"code_length" / c.Rebuild(c.Int32ul, c.len_(c.this.code)),
234+
"key_indexes" / c.Int8ul[V1_SIGNATURE_SLOTS], # pylint: disable=E1136
235+
"flags" / c.BitStruct(
236+
c.Padding(7),
237+
"restore_storage" / c.Flag,
238+
),
239+
"reserved" / c.Padding(52),
240+
"signatures" / c.Bytes(64)[V1_SIGNATURE_SLOTS],
241+
"code" / c.Bytes(c.this.code_length),
242+
c.Terminated,
243+
)
244+
223245
# fmt: on
224246

225247

226248
class FirmwareFormat(Enum):
227249
TREZOR_ONE = 1
228250
TREZOR_T = 2
229251
TREZOR_ONE_V2 = 3
252+
KEEPKEY = 4
230253

231254

232255
FirmwareType = NewType("FirmwareType", c.Container)
@@ -243,6 +266,9 @@ def parse(data: bytes) -> ParsedFirmware:
243266
elif data[:4] == b"TRZF":
244267
version = FirmwareFormat.TREZOR_ONE_V2
245268
cls = FirmwareOneV2
269+
elif data[:4] == b'KPKY':
270+
version = FirmwareFormat.KEEPKEY
271+
cls = FirmwareKeepkey
246272
else:
247273
raise ValueError("Unrecognized firmware image type")
248274

@@ -258,7 +284,7 @@ def digest_onev1(fw: FirmwareType) -> bytes:
258284

259285

260286
def check_sig_v1(
261-
digest: bytes, key_indexes: List[int], signatures: List[bytes]
287+
digest: bytes, key_indexes: List[int], signatures: List[bytes], is_keepkey: bool = False
262288
) -> None:
263289
distinct_key_indexes = set(i for i in key_indexes if i != 0)
264290
if not distinct_key_indexes:
@@ -271,15 +297,17 @@ def check_sig_v1(
271297
)
272298
)
273299

300+
bootloader_keys = KEEPKEY_BOOTLOADER_KEYS if is_keepkey else V1_BOOTLOADER_KEYS
301+
274302
for i in range(len(key_indexes)):
275303
key_idx = key_indexes[i]
276304
signature = signatures[i]
277305

278-
if key_idx not in V1_BOOTLOADER_KEYS:
306+
if key_idx not in bootloader_keys:
279307
# unknown pubkey
280308
raise InvalidSignatureError("Unknown key in slot {}".format(i))
281309

282-
pubkey = bytes.fromhex(V1_BOOTLOADER_KEYS[key_idx])[1:]
310+
pubkey = bytes.fromhex(bootloader_keys[key_idx])[1:]
283311
verify = ecdsa.VerifyingKey.from_string(pubkey, curve=ecdsa.curves.SECP256k1)
284312
try:
285313
verify.verify_digest(signature, digest)
@@ -362,6 +390,14 @@ def validate_onev1(fw: FirmwareType, allow_unsigned: bool = False) -> None:
362390
validate_onev2(fw.embedded_onev2, allow_unsigned)
363391

364392

393+
def validate_keepkey(fw: FirmwareType, allow_unsigned: bool = False) -> None:
394+
try:
395+
check_sig_v1(digest_onev1(fw), fw.key_indexes, fw.signatures, True)
396+
except Unsigned:
397+
if not allow_unsigned:
398+
raise
399+
400+
365401
def validate_v2(fw: FirmwareType, skip_vendor_header: bool = False) -> None:
366402
vendor_fingerprint = _header_digest(fw.vendor_header, VendorHeader)
367403
fingerprint = digest_v2(fw)
@@ -405,7 +441,7 @@ def validate_v2(fw: FirmwareType, skip_vendor_header: bool = False) -> None:
405441

406442

407443
def digest(version: FirmwareFormat, fw: FirmwareType) -> bytes:
408-
if version == FirmwareFormat.TREZOR_ONE:
444+
if version == FirmwareFormat.TREZOR_ONE or version == FirmwareFormat.KEEPKEY:
409445
return digest_onev1(fw)
410446
elif version == FirmwareFormat.TREZOR_ONE_V2:
411447
return digest_onev2(fw)
@@ -420,6 +456,8 @@ def validate(
420456
) -> None:
421457
if version == FirmwareFormat.TREZOR_ONE:
422458
return validate_onev1(fw, allow_unsigned)
459+
elif version == FirmwareFormat.KEEPKEY:
460+
return validate_keepkey(fw, allow_unsigned)
423461
elif version == FirmwareFormat.TREZOR_ONE_V2:
424462
return validate_onev2(fw, allow_unsigned)
425463
elif version == FirmwareFormat.TREZOR_T:
@@ -432,15 +470,19 @@ def validate(
432470

433471

434472
@tools.session
435-
def update(client, data):
473+
def update(client, data, fw_version):
436474
if client.features.bootloader_mode is False:
437475
raise RuntimeError("Device must be in bootloader mode")
438476

439477
resp = client.call(messages.FirmwareErase(length=len(data)))
440478

441479
# TREZORv1 method
442480
if isinstance(resp, messages.Success):
443-
resp = client.call(messages.FirmwareUpload(payload=data))
481+
if fw_version == FirmwareFormat.KEEPKEY:
482+
data_hash = hashlib.sha256(data).digest()
483+
resp = client.call(messages.FirmwareUploadKeepkey(payload=data, hash=data_hash))
484+
else:
485+
resp = client.call(messages.FirmwareUpload(payload=data))
444486
if isinstance(resp, messages.Success):
445487
return
446488
else:

hwilib/devices/trezorlib/mapping.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def build_map():
4141

4242

4343
def register_message(msg_class):
44-
if msg_class.MESSAGE_WIRE_TYPE in map_type_to_class:
44+
# Ignore this error for FirmwareUploadKeepkey
45+
if msg_class.MESSAGE_WIRE_TYPE in map_type_to_class and not messages.FirmwareUploadKeepkey:
4546
raise Exception(
4647
"Message for wire type %s is already registered by %s"
4748
% (msg_class.MESSAGE_WIRE_TYPE, get_class(msg_class.MESSAGE_WIRE_TYPE))

hwilib/devices/trezorlib/messages/FirmwareUpload.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,21 @@ def get_fields(cls):
2020
1: ('payload', p.BytesType, 0), # required
2121
2: ('hash', p.BytesType, 0),
2222
}
23+
24+
class FirmwareUploadKeepkey(p.MessageType):
25+
MESSAGE_WIRE_TYPE = 7
26+
27+
def __init__(
28+
self,
29+
payload: bytes = None,
30+
hash: bytes = None,
31+
) -> None:
32+
self.payload = payload
33+
self.hash = hash
34+
35+
@classmethod
36+
def get_fields(cls):
37+
return {
38+
1: ('hash', p.BytesType, 0),
39+
2: ('payload', p.BytesType, 0),
40+
}

hwilib/devices/trezorlib/messages/MessageType.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
GetFeatures = 55
3333
FirmwareErase = 6
3434
FirmwareUpload = 7
35+
FirmwareUploadKeepkey = 7
3536
FirmwareRequest = 8
3637
SelfTest = 32
3738
GetPublicKey = 11

hwilib/devices/trezorlib/messages/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from .Features import Features
2727
from .FirmwareErase import FirmwareErase
2828
from .FirmwareRequest import FirmwareRequest
29-
from .FirmwareUpload import FirmwareUpload
29+
from .FirmwareUpload import FirmwareUpload, FirmwareUploadKeepkey
3030
from .GetAddress import GetAddress
3131
from .GetEntropy import GetEntropy
3232
from .GetFeatures import GetFeatures

hwilib/devices/trezorlib/ui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, passphrase):
6767

6868
def button_request(self, code):
6969
if not self.prompt_shown:
70-
echo("Please confirm action on your Trezor device")
70+
echo("Please confirm action on your device")
7171
if not self.always_prompt:
7272
self.prompt_shown = True
7373

0 commit comments

Comments
 (0)