2018-08-13 16:21:24 +00:00
|
|
|
|
2018-06-13 17:04:18 +00:00
|
|
|
import construct as c
|
|
|
|
import pyblake2
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
from . import cosi, messages as proto, tools
|
|
|
|
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
def bytes_not(data):
|
|
|
|
return bytes(~b & 0xff for b in data)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
|
|
|
|
# fmt: off
|
2018-06-13 17:04:18 +00:00
|
|
|
Toif = c.Struct(
|
|
|
|
"magic" / c.Const(b"TOI"),
|
|
|
|
"format" / c.Enum(c.Byte, full_color=b"f", grayscale=b"g"),
|
|
|
|
"width" / c.Int16ul,
|
|
|
|
"height" / c.Int16ul,
|
|
|
|
"data" / c.Prefixed(c.Int32ul, c.GreedyBytes),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
VendorTrust = c.Transformed(c.BitStruct(
|
|
|
|
"reserved" / c.Padding(9),
|
|
|
|
"show_vendor_string" / c.Flag,
|
|
|
|
"require_user_click" / c.Flag,
|
|
|
|
"red_background" / c.Flag,
|
|
|
|
"delay" / c.BitsInteger(4),
|
|
|
|
), bytes_not, 2, bytes_not, 2)
|
|
|
|
|
|
|
|
|
|
|
|
VendorHeader = c.Struct(
|
|
|
|
"_start_offset" / c.Tell,
|
|
|
|
"magic" / c.Const(b"TRZV"),
|
|
|
|
"_header_len" / c.Padding(4),
|
|
|
|
"expiry" / c.Int32ul,
|
|
|
|
"version" / c.Struct(
|
|
|
|
"major" / c.Int8ul,
|
|
|
|
"minor" / c.Int8ul,
|
|
|
|
),
|
|
|
|
"vendor_sigs_required" / c.Int8ul,
|
|
|
|
"vendor_sigs_n" / c.Rebuild(c.Int8ul, c.len_(c.this.pubkeys)),
|
|
|
|
"vendor_trust" / VendorTrust,
|
|
|
|
"reserved" / c.Padding(14),
|
|
|
|
"pubkeys" / c.Bytes(32)[c.this.vendor_sigs_n],
|
|
|
|
"vendor_string" / c.Aligned(4, c.PascalString(c.Int8ul, "utf-8")),
|
|
|
|
"vendor_image" / Toif,
|
|
|
|
"_data_end_offset" / c.Tell,
|
|
|
|
|
|
|
|
c.Padding(-(c.this._data_end_offset + 65) % 512),
|
|
|
|
"sigmask" / c.Byte,
|
|
|
|
"signature" / c.Bytes(64),
|
|
|
|
|
|
|
|
"_end_offset" / c.Tell,
|
|
|
|
"header_len" / c.Pointer(
|
|
|
|
c.this._start_offset + 4,
|
|
|
|
c.Rebuild(c.Int32ul, c.this._end_offset - c.this._start_offset)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
VersionLong = c.Struct(
|
|
|
|
"major" / c.Int8ul,
|
|
|
|
"minor" / c.Int8ul,
|
|
|
|
"patch" / c.Int8ul,
|
|
|
|
"build" / c.Int8ul,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
FirmwareHeader = c.Struct(
|
|
|
|
"_start_offset" / c.Tell,
|
|
|
|
"magic" / c.Const(b"TRZF"),
|
|
|
|
"_header_len" / c.Padding(4),
|
|
|
|
"expiry" / c.Int32ul,
|
|
|
|
"code_length" / c.Rebuild(
|
|
|
|
c.Int32ul,
|
|
|
|
lambda this:
|
|
|
|
len(this._.code) if "code" in this._
|
|
|
|
else (this.code_length or 0)),
|
|
|
|
"version" / VersionLong,
|
|
|
|
"fix_version" / VersionLong,
|
|
|
|
"reserved" / c.Padding(8),
|
|
|
|
"hashes" / c.Bytes(32)[16],
|
|
|
|
|
|
|
|
"reserved" / c.Padding(415),
|
|
|
|
"sigmask" / c.Byte,
|
|
|
|
"signature" / c.Bytes(64),
|
|
|
|
|
|
|
|
"_end_offset" / c.Tell,
|
2018-08-13 16:21:24 +00:00
|
|
|
"header_len" / c.Pointer(
|
|
|
|
c.this._start_offset + 4,
|
|
|
|
c.Rebuild(c.Int32ul, c.this._end_offset - c.this._start_offset)
|
|
|
|
),
|
2018-06-13 17:04:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
Firmware = c.Struct(
|
|
|
|
"vendor_header" / VendorHeader,
|
|
|
|
"firmware_header" / FirmwareHeader,
|
|
|
|
"code" / c.Bytes(c.this.firmware_header.code_length),
|
|
|
|
c.Terminated,
|
|
|
|
)
|
2018-08-13 16:21:24 +00:00
|
|
|
# fmt: on
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
def validate_firmware(filename):
|
|
|
|
with open(filename, "rb") as f:
|
|
|
|
data = f.read()
|
2018-08-13 16:21:24 +00:00
|
|
|
if data[:6] == b"54525a":
|
2018-09-12 22:44:08 +00:00
|
|
|
data = bytes.fromhex(data.decode())
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
fw = Firmware.parse(data)
|
|
|
|
except Exception as e:
|
|
|
|
raise ValueError("Invalid firmware image format") from e
|
|
|
|
|
|
|
|
vendor = fw.vendor_header
|
|
|
|
header = fw.firmware_header
|
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
print(
|
|
|
|
"Vendor header from {}, version {}.{}".format(
|
|
|
|
vendor.vendor_string, vendor.version.major, vendor.version.minor
|
|
|
|
)
|
|
|
|
)
|
|
|
|
print(
|
|
|
|
"Firmware version {v.major}.{v.minor}.{v.patch} build {v.build}".format(
|
|
|
|
v=header.version
|
|
|
|
)
|
|
|
|
)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
# rebuild header without signatures
|
|
|
|
stripped_header = header.copy()
|
|
|
|
stripped_header.sigmask = 0
|
2018-08-13 16:21:24 +00:00
|
|
|
stripped_header.signature = b"\0" * 64
|
2018-06-13 17:04:18 +00:00
|
|
|
header_bytes = FirmwareHeader.build(stripped_header)
|
|
|
|
digest = pyblake2.blake2s(header_bytes).digest()
|
|
|
|
|
2018-09-12 22:44:08 +00:00
|
|
|
print("Fingerprint: {}".format(digest.hex()))
|
2018-06-13 17:04:18 +00:00
|
|
|
|
2018-08-13 16:21:24 +00:00
|
|
|
global_pk = cosi.combine_keys(
|
|
|
|
vendor.pubkeys[i] for i in range(8) if header.sigmask & (1 << i)
|
|
|
|
)
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
cosi.verify(header.signature, digest, global_pk)
|
|
|
|
print("Signature OK")
|
2018-08-10 14:05:14 +00:00
|
|
|
except Exception:
|
2018-06-13 17:04:18 +00:00
|
|
|
print("Signature FAILED")
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
2018-08-10 14:05:14 +00:00
|
|
|
# ====== Client functions ====== #
|
2018-06-13 17:04:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
@tools.session
|
|
|
|
def update(client, fp):
|
|
|
|
if client.features.bootloader_mode is False:
|
|
|
|
raise RuntimeError("Device must be in bootloader mode")
|
|
|
|
|
|
|
|
data = fp.read()
|
|
|
|
|
|
|
|
resp = client.call(proto.FirmwareErase(length=len(data)))
|
|
|
|
if isinstance(resp, proto.Failure) and resp.code == proto.FailureType.FirmwareError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# TREZORv1 method
|
|
|
|
if isinstance(resp, proto.Success):
|
2018-06-13 17:35:01 +00:00
|
|
|
# fingerprint = hashlib.sha256(data[256:]).hexdigest()
|
2018-06-13 17:04:18 +00:00
|
|
|
# LOG.debug("Firmware fingerprint: " + fingerprint)
|
|
|
|
resp = client.call(proto.FirmwareUpload(payload=data))
|
|
|
|
if isinstance(resp, proto.Success):
|
|
|
|
return True
|
2018-08-13 16:21:24 +00:00
|
|
|
elif (
|
|
|
|
isinstance(resp, proto.Failure)
|
|
|
|
and resp.code == proto.FailureType.FirmwareError
|
|
|
|
):
|
2018-06-13 17:04:18 +00:00
|
|
|
return False
|
|
|
|
raise RuntimeError("Unexpected result %s" % resp)
|
|
|
|
|
|
|
|
# TREZORv2 method
|
|
|
|
if isinstance(resp, proto.FirmwareRequest):
|
|
|
|
import pyblake2
|
2018-08-13 16:21:24 +00:00
|
|
|
|
2018-06-13 17:04:18 +00:00
|
|
|
while True:
|
2018-08-13 16:21:24 +00:00
|
|
|
payload = data[resp.offset : resp.offset + resp.length]
|
2018-06-13 17:04:18 +00:00
|
|
|
digest = pyblake2.blake2s(payload).digest()
|
|
|
|
resp = client.call(proto.FirmwareUpload(payload=payload, hash=digest))
|
|
|
|
if isinstance(resp, proto.FirmwareRequest):
|
|
|
|
continue
|
|
|
|
elif isinstance(resp, proto.Success):
|
|
|
|
return True
|
2018-08-13 16:21:24 +00:00
|
|
|
elif (
|
|
|
|
isinstance(resp, proto.Failure)
|
|
|
|
and resp.code == proto.FailureType.FirmwareError
|
|
|
|
):
|
2018-06-13 17:04:18 +00:00
|
|
|
return False
|
|
|
|
raise RuntimeError("Unexpected result %s" % resp)
|
|
|
|
|
|
|
|
raise RuntimeError("Unexpected message %s" % resp)
|