You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/tools/binctl

244 lines
7.6 KiB

#!/usr/bin/env python3
import sys
import struct
import binascii
import ed25519
import pyblake2
# loader/firmware headers specification: https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md
def get_sig(data):
print('Enter privkey: ', end='')
seckey = binascii.unhexlify(input())
signkey = ed25519.SigningKey(seckey)
digest = pyblake2.blake2s(data).digest()
sigidx = 0x01 # (1 _ _ _ _)
sig = signkey.sign(digest)
return sigidx, sig
class LoaderImage:
def __init__(self, data):
header = struct.unpack('<4sIIIBBBB171sB64s', data[:256])
self.magic, \
self.hdrlen, \
self.expiry, \
self.codelen, \
self.vmajor, \
self.vminor, \
self.vpatch, \
self.vbuild, \
self.reserved, \
self.sigidx, \
self.sig = header
assert self.magic == b'TRZL'
assert self.hdrlen == 256
assert self.codelen + self.hdrlen >= 4 * 1024
assert self.codelen + self.hdrlen <= 64 * 1024 + 7 * 128 * 1024
assert (self.codelen + self.hdrlen) % 512 == 0
assert self.reserved == 171 * b'\x00'
self.code = data[self.hdrlen:]
assert len(self.code) == self.codelen
def print(self):
print('TREZOR Loader Image')
print(' * magic :', self.magic.decode('ascii'))
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(' * sigidx :', self.sigidx)
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
def serialize_header(self, sig=True):
header = struct.pack('<4sIIIBBBB171s', \
self.magic, self.hdrlen, self.expiry, self.codelen, \
self.vmajor, self.vminor, self.vpatch, self.vbuild, \
self.reserved)
if sig:
header += struct.pack('<B64s', self.sigidx, self.sig)
else:
header += 65 * b'\x00'
assert len(header) == self.hdrlen
return header
def sign(self):
header = self.serialize_header(sig=False)
data = header + self.code
assert len(data) == self.hdrlen + self.codelen
self.sigidx, self.sig = get_sig(data)
def write(self, filename):
with open(filename, 'wb') as f:
f.write(self.serialize_header())
f.write(self.code)
class VendorHeader:
def __init__(self, data):
header = struct.unpack('<4sIIBBBB', data[:16])
self.magic, \
self.hdrlen, \
self.expiry, \
self.vmajor, \
self.vminor, \
self.vsig_m, \
self.vsig_n = header
assert self.magic == b'TRZF'
assert self.vsig_m > 0 and self.vsig_m <= self.vsig_n
assert self.vsig_n > 0 and self.vsig_n <= 8
p = 16
self.vpub = []
for _ in range(self.vsig_n):
self.vpub.append(data[p:p + 32])
p += 32
self.vstr_len = data[p]
p += 1
self.vstr = data[p:p + self.vstr_len]
p += self.vstr_len
self.vimg_len, _ = struct.unpack('<H', data[p: p + 2])
p += 2
self.vimg = data[p:p + self.vimg_len]
p += self.vimg_len
self.sigidx = data[p]
p += 1
self.sig = data[p:p+64]
assert len(data) == 4 + 4 + 4 + 1 + 1 + 1 + 1 + \
32 * len(self.vpub) + \
1 + self.vstr_len + \
2 + self.vimg_len + \
1 + 64
def print(self):
print('TREZOR Vendor Header')
print(' * magic :', self.magic.decode('ascii'))
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))
for i in range(self.vsig_n):
print(' * vpub #%d :' % (i + 1), binascii.hexlify(self.vpub[i]).decode('ascii'))
print(' * vstr :', self.vstr.decode('ascii'))
print(' * vimg : (%d bytes)', len(self.vimg))
print(' * sigidx :', self.sigidx)
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
def serialize_header(self, sig=True):
header = struct.pack('<4sIIBBBB', \
self.magic, self.hdrlen, self.expiry, \
self.vmajor, self.vminor, \
self.vsig_m, self.vsig_n)
for i in range(self.vsig_n):
header += self.vpub[i]
header += struct.pack('<B', self.vstr_len) + self.vstr
header += struct.pack('<H', self.vimg_len) + self.vimg
if sig:
header += struct.pack('<B64s', self.sigidx, self.sig)
else:
header += 65 * b'\x00'
assert len(header) == self.hdrlen
return header
def sign(self):
header = self.serialize_header(sig=False)
self.sigidx, self.sig = get_sig(header)
def write(self, filename):
with open(filename, 'wb') as f:
f.write(self.serialize_header())
class FirmwareImage:
def __init__(self, data):
header = struct.unpack('<4sIIIBBBB171sB64s', data[:256])
self.magic, \
self.hdrlen, \
self.expiry, \
self.codelen, \
self.vmajor, \
self.vminor, \
self.vpatch, \
self.vbuild, \
self.reserved, \
self.sigidx, \
self.sig = header
assert self.magic == b'TRZF'
assert self.hdrlen == 256
assert self.codelen % 4 == 0
assert self.reserved == 171 * b'\x00'
self.code = data[self.hdrlen:]
assert len(self.code) == self.codelen
def print(self):
print('TREZOR Firmware Image')
print(' * magic :', self.magic.decode('ascii'))
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(' * sigidx :', self.sigidx)
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
def serialize_header(self, sig=True):
header = struct.pack('<4sIIIBBBB171s', \
self.magic, self.hdrlen, self.expiry, self.codelen, \
self.vmajor, self.vminor, self.vpatch, self.vbuild, \
self.reserved)
if sig:
header += struct.pack('<B64s', self.sigidx, self.sig)
else:
header += 65 * b'\x00'
assert len(header) == self.hdrlen
return header
def sign(self):
header = self.serialize_header(sig=False)
data = header + self.code
assert len(data) == self.hdrlen + self.codelen
self.sigidx, self.sig = get_sig(data)
def write(self, filename):
with open(filename, 'wb') as f:
f.write(self.serialize_header())
f.write(self.code)
def binopen(filename):
print()
print('Opening file', filename)
print()
data = open(filename, 'rb').read()
magic = data[:4]
if magic == b'TRZL':
return LoaderImage(data)
if magic == b'TRZV':
return VendorHeader(data)
if magic == b'TRZF':
return FirmwareImage(data)
raise Exception('Unknown file format')
def main():
if len(sys.argv) < 2:
print('Usage: firmwarectl file.bin [-s]')
return 1
fn = sys.argv[1]
sign = len(sys.argv) > 2 and sys.argv[2] == '-s'
b = binopen(fn)
b.print()
if sign:
print()
b.sign()
print()
b.print()
b.write(fn)
if __name__ == '__main__':
main()