1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-24 23:38:09 +00:00
trezor-firmware/tools/binctl

360 lines
12 KiB
Plaintext
Raw Normal View History

2017-09-25 14:01:22 +00:00
#!/usr/bin/env python3
2017-08-07 09:50:12 +00:00
from __future__ import print_function
import sys
import struct
import binascii
import pyblake2
from trezorlib import cosi
def format_sigmask(sigmask):
2018-07-31 09:35:09 +00:00
bits = [str(b + 1) if sigmask & (1 << b) else "." for b in range(8)]
return "0x%02x = [%s]" % (sigmask, " ".join(bits))
def format_vtrust(vtrust):
2018-07-31 09:35:09 +00:00
bits = [str(b) if vtrust & (1 << b) == 0 else "." for b in range(16)]
2018-01-28 10:24:50 +00:00
# see docs/bootloader.md for vtrust constants
2018-07-31 09:35:09 +00:00
desc = ""
2018-01-28 10:24:50 +00:00
wait = (vtrust & 0x000F) ^ 0x000F
if wait > 0:
2018-07-31 09:35:09 +00:00
desc = "WAIT_%d" % wait
2018-01-28 10:24:50 +00:00
if vtrust & 0x0010 == 0:
2018-07-31 09:35:09 +00:00
desc += " RED"
2018-01-28 10:24:50 +00:00
if vtrust & 0x0020 == 0:
2018-07-31 09:35:09 +00:00
desc += " CLICK"
2018-01-28 10:24:50 +00:00
if vtrust & 0x0040 == 0:
2018-07-31 09:35:09 +00:00
desc += " STRING"
return "%d = [%s] = [%s]" % (vtrust, " ".join(bits), desc)
2017-04-10 17:24:21 +00:00
# bootloader/firmware headers specification: https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md
2018-07-31 09:35:09 +00:00
IMAGE_HEADER_SIZE = 1024
IMAGE_SIG_SIZE = 65
IMAGE_CHUNK_SIZE = 128 * 1024
BOOTLOADER_SECTORS_COUNT = 1
FIRMWARE_SECTORS_COUNT = 6 + 7
2017-08-07 09:50:12 +00:00
class BinImage(object):
def __init__(self, data, magic, max_size):
2018-07-31 09:35:09 +00:00
header = struct.unpack("<4sIIIBBBBBBBB8s512s415sB64s", data[:IMAGE_HEADER_SIZE])
self.magic, self.hdrlen, self.expiry, self.codelen, self.vmajor, self.vminor, self.vpatch, self.vbuild, self.fix_vmajor, self.fix_vminor, self.fix_vpatch, self.fix_vbuild, self.reserved1, self.hashes, self.reserved2, self.sigmask, self.sig = (
header
)
assert self.magic == magic
assert self.hdrlen == IMAGE_HEADER_SIZE
total_len = self.hdrlen + self.codelen
assert total_len % 512 == 0
assert total_len >= 4 * 1024
assert total_len <= max_size
2018-07-31 09:35:09 +00:00
assert self.reserved1 == 8 * b"\x00"
assert self.reserved2 == 415 * b"\x00"
self.code = data[self.hdrlen :]
assert len(self.code) == self.codelen
def print(self):
2018-07-31 09:35:09 +00:00
if self.magic == b"TRZF":
print("TREZOR Firmware Image")
total_len = self.vhdrlen + self.hdrlen + self.codelen
2018-07-31 09:35:09 +00:00
elif self.magic == b"TRZB":
print("TREZOR Bootloader Image")
total_len = self.hdrlen + self.codelen
else:
2018-07-31 09:35:09 +00:00
print("TREZOR Unknown Image")
print(" * magic :", self.magic.decode())
print(" * hdrlen :", self.hdrlen)
print(" * expiry :", self.expiry)
print(" * codelen :", self.codelen)
print(
" * version : %d.%d.%d.%d"
% (self.vmajor, self.vminor, self.vpatch, self.vbuild)
)
print(
" * fixver : %d.%d.%d.%d"
% (self.fix_vmajor, self.fix_vminor, self.fix_vpatch, self.fix_vbuild)
)
print(" * hashes: %s" % ("OK" if self.check_hashes() else "INCORRECT"))
for i in range(16):
2018-07-31 09:35:09 +00:00
print(
" - %02d : %s"
% (i, binascii.hexlify(self.hashes[i * 32 : i * 32 + 32]).decode())
)
print(" * sigmask :", format_sigmask(self.sigmask))
print(" * sig :", binascii.hexlify(self.sig).decode())
print(" * total : %d bytes" % total_len)
print(" * fngprnt :", self.fingerprint())
print()
def compute_hashes(self):
2018-07-31 09:35:09 +00:00
if self.magic == b"TRZF":
hdrlen = self.vhdrlen + self.hdrlen
else:
hdrlen = self.hdrlen
2018-07-31 09:35:09 +00:00
hashes = b""
for i in range(16):
if i == 0:
2018-07-31 09:35:09 +00:00
d = self.code[: IMAGE_CHUNK_SIZE - hdrlen]
else:
s = IMAGE_CHUNK_SIZE - hdrlen + (i - 1) * IMAGE_CHUNK_SIZE
2018-07-31 09:35:09 +00:00
d = self.code[s : s + IMAGE_CHUNK_SIZE]
if len(d) > 0:
h = pyblake2.blake2s(d).digest()
else:
2018-07-31 09:35:09 +00:00
h = 32 * b"\x00"
hashes += h
return hashes
def check_hashes(self):
return self.hashes == self.compute_hashes()
def update_hashes(self):
self.hashes = self.compute_hashes()
def serialize_header(self, sig=True):
2018-07-31 09:35:09 +00:00
header = struct.pack(
"<4sIIIBBBBBBBB8s512s415s",
self.magic,
self.hdrlen,
self.expiry,
self.codelen,
self.vmajor,
self.vminor,
self.vpatch,
self.vbuild,
self.fix_vmajor,
self.fix_vminor,
self.fix_vpatch,
self.fix_vbuild,
self.reserved1,
self.hashes,
self.reserved2,
)
if sig:
2018-07-31 09:35:09 +00:00
header += struct.pack("<B64s", self.sigmask, self.sig)
else:
2018-07-31 09:35:09 +00:00
header += IMAGE_SIG_SIZE * b"\x00"
assert len(header) == self.hdrlen
return header
def fingerprint(self):
return pyblake2.blake2s(self.serialize_header(sig=False)).hexdigest()
def sign(self, sigmask, signature):
header = self.serialize_header(sig=False)
data = header + self.code
assert len(data) == self.hdrlen + self.codelen
self.sigmask = sigmask
self.sig = signature
def write(self, filename):
2018-07-31 09:35:09 +00:00
with open(filename, "wb") as f:
f.write(self.serialize_header())
f.write(self.code)
class FirmwareImage(BinImage):
def __init__(self, data, vhdrlen):
2018-07-31 09:35:09 +00:00
super(FirmwareImage, self).__init__(
data[vhdrlen:],
magic=b"TRZF",
max_size=FIRMWARE_SECTORS_COUNT * IMAGE_CHUNK_SIZE,
)
self.vhdrlen = vhdrlen
self.vheader = data[:vhdrlen]
def write(self, filename):
2018-07-31 09:35:09 +00:00
with open(filename, "wb") as f:
f.write(self.vheader)
f.write(self.serialize_header())
f.write(self.code)
2017-09-05 21:15:47 +00:00
2017-04-10 17:24:21 +00:00
class BootloaderImage(BinImage):
def __init__(self, data):
2018-07-31 09:35:09 +00:00
super(BootloaderImage, self).__init__(
data, magic=b"TRZB", max_size=BOOTLOADER_SECTORS_COUNT * IMAGE_CHUNK_SIZE
)
2017-08-07 09:50:12 +00:00
class VendorHeader(object):
def __init__(self, data):
2018-07-31 09:35:09 +00:00
header = struct.unpack("<4sIIBBBBH", data[:18])
self.magic, self.hdrlen, self.expiry, self.vmajor, self.vminor, self.vsig_m, self.vsig_n, self.vtrust = (
header
)
assert self.magic == b"TRZV"
data = data[: self.hdrlen] # strip remaining data (firmware)
assert self.vsig_m > 0 and self.vsig_m <= self.vsig_n
assert self.vsig_n > 0 and self.vsig_n <= 8
2017-10-05 15:31:05 +00:00
p = 32
self.vpub = []
for _ in range(self.vsig_n):
2018-07-31 09:35:09 +00:00
self.vpub.append(data[p : p + 32])
p += 32
self.vstr_len = data[p]
p += 1
2018-07-31 09:35:09 +00:00
self.vstr = data[p : p + self.vstr_len]
p += self.vstr_len
vstr_pad = -p & 3
p += vstr_pad
self.vimg_len = len(data) - IMAGE_SIG_SIZE - p
2018-07-31 09:35:09 +00:00
self.vimg = data[p : p + self.vimg_len]
p += self.vimg_len
self.sigmask = data[p]
p += 1
2018-07-31 09:35:09 +00:00
self.sig = data[p : p + 64]
assert (
len(data)
== 4
+ 4
+ 4
+ 1
+ 1
+ 1
+ 1
+ 1
+ 15
+ 32 * len(self.vpub)
+ 1
+ self.vstr_len
+ vstr_pad
+ self.vimg_len
+ IMAGE_SIG_SIZE
)
assert len(data) % 512 == 0
def print(self):
2018-07-31 09:35:09 +00:00
print("TREZOR Vendor Header")
print(" * magic :", self.magic.decode())
print(" * hdrlen :", self.hdrlen)
print(" * expiry :", self.expiry)
print(" * version : %d.%d" % (self.vmajor, self.vminor))
print(" * scheme : %d out of %d" % (self.vsig_m, self.vsig_n))
print(" * trust :", format_vtrust(self.vtrust))
for i in range(self.vsig_n):
2018-07-31 09:35:09 +00:00
print(" * vpub #%d :" % (i + 1), binascii.hexlify(self.vpub[i]).decode())
print(" * vstr :", self.vstr.decode())
print(" * vhash :", binascii.hexlify(self.vhash()).decode())
print(" * vimg : (%d bytes)" % len(self.vimg))
print(" * sigmask :", format_sigmask(self.sigmask))
print(" * sig :", binascii.hexlify(self.sig).decode())
print(" * fngprnt :", self.fingerprint())
print()
def serialize_header(self, sig=True):
2018-07-31 09:35:09 +00:00
header = struct.pack(
"<4sIIBBBBH",
self.magic,
self.hdrlen,
self.expiry,
self.vmajor,
self.vminor,
self.vsig_m,
self.vsig_n,
self.vtrust,
)
header += 14 * b"\x00"
for i in range(self.vsig_n):
header += self.vpub[i]
2018-07-31 09:35:09 +00:00
header += struct.pack("<B", self.vstr_len) + self.vstr
header += (-len(header) & 3) * b"\x00" # vstr_pad
header += self.vimg
if sig:
2018-07-31 09:35:09 +00:00
header += struct.pack("<B64s", self.sigmask, self.sig)
else:
2018-07-31 09:35:09 +00:00
header += IMAGE_SIG_SIZE * b"\x00"
assert len(header) == self.hdrlen
return header
def fingerprint(self):
return pyblake2.blake2s(self.serialize_header(sig=False)).hexdigest()
def vhash(self):
h = pyblake2.blake2s()
2018-07-31 09:35:09 +00:00
h.update(struct.pack("<BB", self.vsig_m, self.vsig_n))
for i in range(8):
if i < self.vsig_n:
h.update(self.vpub[i])
else:
2018-07-31 09:35:09 +00:00
h.update(b"\x00" * 32)
return h.digest()
def sign(self, sigmask, signature):
2018-01-26 14:23:40 +00:00
header = self.serialize_header(sig=False)
assert len(header) == self.hdrlen
self.sigmask = sigmask
self.sig = signature
def write(self, filename):
2018-07-31 09:35:09 +00:00
with open(filename, "wb") as f:
f.write(self.serialize_header())
def binopen(filename):
2018-07-31 09:35:09 +00:00
data = open(filename, "rb").read()
magic = data[:4]
2018-07-31 09:35:09 +00:00
if magic == b"TRZB":
2017-04-10 17:24:21 +00:00
return BootloaderImage(data)
2018-07-31 09:35:09 +00:00
if magic == b"TRZV":
vheader = VendorHeader(data)
if len(data) == vheader.hdrlen:
return vheader
vheader.print()
2018-07-31 09:35:09 +00:00
subdata = data[vheader.hdrlen :]
if subdata[:4] == b"TRZF":
firmware = FirmwareImage(data, vheader.hdrlen)
# check signatures against signing keys in the vendor header
if firmware.sigmask > 0:
pk = [vheader.vpub[i] for i in range(8) if firmware.sigmask & (1 << i)]
global_pk = cosi.combine_keys(pk)
2018-07-31 09:35:09 +00:00
hdr = (
subdata[: IMAGE_HEADER_SIZE - IMAGE_SIG_SIZE]
+ IMAGE_SIG_SIZE * b"\x00"
)
digest = pyblake2.blake2s(hdr).digest()
try:
cosi.verify(firmware.sig, digest, global_pk)
2018-07-31 09:35:09 +00:00
print("Firmware signature OK")
except ValueError:
print("Firmware signature INCORRECT")
else:
2018-07-31 09:35:09 +00:00
print("No firmware signature")
return firmware
2018-07-31 09:35:09 +00:00
if magic == b"TRZF":
return FirmwareImage(data, 0)
2018-07-31 09:35:09 +00:00
raise Exception("Unknown file format")
2017-09-05 21:15:47 +00:00
def main():
if len(sys.argv) < 2:
2018-07-31 09:35:09 +00:00
print("Usage: binctl file.bin [-s sigmask signature] [-h]")
return 1
fn = sys.argv[1]
2018-07-31 09:35:09 +00:00
sign = len(sys.argv) > 2 and sys.argv[2] == "-s"
rehash = len(sys.argv) == 3 and sys.argv[2] == "-h"
b = binopen(fn)
if sign:
sigmask = 0
2018-07-31 09:35:09 +00:00
if ":" in sys.argv[3]:
for idx in sys.argv[3].split(":"):
sigmask |= 1 << (int(idx) - 1)
else:
sigmask = 1 << (int(sys.argv[3]) - 1)
signature = binascii.unhexlify(sys.argv[4])
b.sign(sigmask, signature)
b.write(fn)
if rehash:
b.update_hashes()
b.write(fn)
b.print()
2018-07-31 09:35:09 +00:00
if __name__ == "__main__":
main()