2017-08-07 09:50:12 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
2017-02-09 15:26:15 +00:00
|
|
|
import sys
|
|
|
|
import struct
|
|
|
|
import binascii
|
|
|
|
import ed25519
|
2017-03-22 00:53:25 +00:00
|
|
|
import pyblake2
|
2017-02-09 15:26:15 +00:00
|
|
|
|
2017-02-09 17:14:10 +00:00
|
|
|
|
2017-04-08 16:23:08 +00:00
|
|
|
def sign_data(seckey, data):
|
2017-02-09 15:26:15 +00:00
|
|
|
signkey = ed25519.SigningKey(seckey)
|
2017-03-22 00:53:25 +00:00
|
|
|
digest = pyblake2.blake2s(data).digest()
|
2017-04-08 16:23:08 +00:00
|
|
|
return signkey.sign(digest)
|
2017-02-09 15:26:15 +00:00
|
|
|
|
2017-09-05 21:15:47 +00:00
|
|
|
|
2017-04-06 16:57:51 +00:00
|
|
|
def format_sigmask(sigmask):
|
2017-09-05 21:15:47 +00:00
|
|
|
bits = [str(b + 1) if sigmask & (1 << b) else '.' for b in range(8)]
|
2017-04-06 16:57:51 +00:00
|
|
|
return '0x%02x = [%s]' % (sigmask, ' '.join(bits))
|
|
|
|
|
|
|
|
|
2017-04-10 17:24:21 +00:00
|
|
|
# bootloader/firmware headers specification: https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md
|
2017-02-09 15:26:15 +00:00
|
|
|
|
2017-04-01 00:32:05 +00:00
|
|
|
|
2017-08-07 09:50:12 +00:00
|
|
|
class BinImage(object):
|
2017-04-01 00:32:05 +00:00
|
|
|
|
|
|
|
def __init__(self, data, magic, max_size):
|
2017-03-31 21:54:59 +00:00
|
|
|
header = struct.unpack('<4sIIIBBBB427sB64s', data[:512])
|
2017-03-30 20:58:00 +00:00
|
|
|
self.magic, \
|
|
|
|
self.hdrlen, \
|
|
|
|
self.expiry, \
|
|
|
|
self.codelen, \
|
|
|
|
self.vmajor, \
|
|
|
|
self.vminor, \
|
|
|
|
self.vpatch, \
|
|
|
|
self.vbuild, \
|
|
|
|
self.reserved, \
|
2017-04-01 00:32:05 +00:00
|
|
|
self.sigmask, \
|
2017-03-30 20:58:00 +00:00
|
|
|
self.sig = header
|
2017-04-01 00:32:05 +00:00
|
|
|
assert self.magic == magic
|
2017-03-31 21:54:59 +00:00
|
|
|
assert self.hdrlen == 512
|
2017-04-01 00:32:05 +00:00
|
|
|
total_len = self.hdrlen + self.codelen
|
|
|
|
assert total_len % 512 == 0
|
|
|
|
assert total_len >= 4 * 1024
|
|
|
|
assert total_len <= max_size
|
2017-03-31 21:54:59 +00:00
|
|
|
assert self.reserved == 427 * b'\x00'
|
2017-02-17 16:11:34 +00:00
|
|
|
self.code = data[self.hdrlen:]
|
2017-02-09 15:26:15 +00:00
|
|
|
assert len(self.code) == self.codelen
|
|
|
|
|
|
|
|
def print(self):
|
2017-04-01 00:32:05 +00:00
|
|
|
if self.magic == b'TRZF':
|
|
|
|
print('TREZOR Firmware Image')
|
2017-04-06 14:58:16 +00:00
|
|
|
total_len = self.vhdrlen + self.hdrlen + self.codelen
|
2017-04-10 17:24:21 +00:00
|
|
|
elif self.magic == b'TRZB':
|
|
|
|
print('TREZOR Bootloader Image')
|
2017-04-06 14:58:16 +00:00
|
|
|
total_len = self.hdrlen + self.codelen
|
2017-04-01 00:32:05 +00:00
|
|
|
else:
|
|
|
|
print('TREZOR Unknown Image')
|
2017-02-09 15:26:15 +00:00
|
|
|
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))
|
2017-04-06 16:57:51 +00:00
|
|
|
print(' * sigmask :', format_sigmask(self.sigmask))
|
2017-02-09 15:26:15 +00:00
|
|
|
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
|
2017-04-06 14:58:16 +00:00
|
|
|
print(' * total : %d bytes' % total_len)
|
|
|
|
print()
|
2017-02-09 15:26:15 +00:00
|
|
|
|
2017-03-30 20:58:00 +00:00
|
|
|
def serialize_header(self, sig=True):
|
2017-09-05 21:15:47 +00:00
|
|
|
header = struct.pack('<4sIIIBBBB427s',
|
|
|
|
self.magic, self.hdrlen, self.expiry, self.codelen,
|
|
|
|
self.vmajor, self.vminor, self.vpatch, self.vbuild,
|
|
|
|
self.reserved)
|
2017-02-09 15:26:15 +00:00
|
|
|
if sig:
|
2017-04-01 00:32:05 +00:00
|
|
|
header += struct.pack('<B64s', self.sigmask, self.sig)
|
2017-02-09 15:26:15 +00:00
|
|
|
else:
|
|
|
|
header += 65 * b'\x00'
|
2017-02-17 16:11:34 +00:00
|
|
|
assert len(header) == self.hdrlen
|
2017-02-09 15:26:15 +00:00
|
|
|
return header
|
|
|
|
|
2017-04-08 16:23:08 +00:00
|
|
|
def sign(self, sigmask, seckey):
|
2017-03-30 20:58:00 +00:00
|
|
|
header = self.serialize_header(sig=False)
|
2017-02-09 15:26:15 +00:00
|
|
|
data = header + self.code
|
2017-02-17 16:11:34 +00:00
|
|
|
assert len(data) == self.hdrlen + self.codelen
|
2017-04-08 16:23:08 +00:00
|
|
|
self.sigmask = sigmask
|
|
|
|
self.sig = sign_data(seckey, data)
|
2017-02-09 15:26:15 +00:00
|
|
|
|
|
|
|
def write(self, filename):
|
|
|
|
with open(filename, 'wb') as f:
|
2017-03-30 20:58:00 +00:00
|
|
|
f.write(self.serialize_header())
|
2017-02-09 15:26:15 +00:00
|
|
|
f.write(self.code)
|
|
|
|
|
|
|
|
|
2017-04-01 00:32:05 +00:00
|
|
|
class FirmwareImage(BinImage):
|
|
|
|
|
2017-04-01 13:45:50 +00:00
|
|
|
def __init__(self, data, vhdrlen):
|
2017-09-05 21:15:47 +00:00
|
|
|
super(FirmwareImage, self).__init__(data[vhdrlen:], magic=b'TRZF', max_size=7 * 128 * 1024)
|
2017-04-06 14:58:16 +00:00
|
|
|
self.vhdrlen = vhdrlen
|
|
|
|
self.vheader = data[:vhdrlen]
|
2017-04-01 00:32:05 +00:00
|
|
|
|
2017-04-01 13:45:50 +00:00
|
|
|
def write(self, filename):
|
|
|
|
with open(filename, 'wb') as f:
|
|
|
|
f.write(self.vheader)
|
|
|
|
f.write(self.serialize_header())
|
|
|
|
f.write(self.code)
|
2017-04-01 00:32:05 +00:00
|
|
|
|
2017-09-05 21:15:47 +00:00
|
|
|
|
2017-04-10 17:24:21 +00:00
|
|
|
class BootloaderImage(BinImage):
|
2017-04-01 00:32:05 +00:00
|
|
|
|
|
|
|
def __init__(self, data):
|
2017-09-05 21:15:47 +00:00
|
|
|
super(BootloaderImage, self).__init__(data, magic=b'TRZB', max_size=64 * 1024 + 7 * 128 * 1024)
|
2017-04-01 00:32:05 +00:00
|
|
|
|
|
|
|
|
2017-08-07 09:50:12 +00:00
|
|
|
class VendorHeader(object):
|
2017-02-09 15:26:15 +00:00
|
|
|
|
|
|
|
def __init__(self, data):
|
2017-02-09 17:14:10 +00:00
|
|
|
header = struct.unpack('<4sIIBBBB', data[:16])
|
2017-03-30 20:58:00 +00:00
|
|
|
self.magic, \
|
|
|
|
self.hdrlen, \
|
|
|
|
self.expiry, \
|
|
|
|
self.vmajor, \
|
|
|
|
self.vminor, \
|
|
|
|
self.vsig_m, \
|
|
|
|
self.vsig_n = header
|
2017-04-01 13:45:50 +00:00
|
|
|
assert self.magic == b'TRZV'
|
2017-02-09 17:14:10 +00:00
|
|
|
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
|
2017-04-01 13:45:50 +00:00
|
|
|
vstr_pad = -p & 3
|
|
|
|
p += vstr_pad
|
|
|
|
self.vimg_len = len(data) - 65 - p
|
2017-02-09 17:14:10 +00:00
|
|
|
self.vimg = data[p:p + self.vimg_len]
|
|
|
|
p += self.vimg_len
|
2017-04-01 00:32:05 +00:00
|
|
|
self.sigmask = data[p]
|
2017-02-09 17:14:10 +00:00
|
|
|
p += 1
|
2017-09-05 21:15:47 +00:00
|
|
|
self.sig = data[p:p + 64]
|
2017-02-09 17:14:10 +00:00
|
|
|
assert len(data) == 4 + 4 + 4 + 1 + 1 + 1 + 1 + \
|
|
|
|
32 * len(self.vpub) + \
|
2017-04-01 13:45:50 +00:00
|
|
|
1 + self.vstr_len + vstr_pad + \
|
|
|
|
self.vimg_len + \
|
2017-02-09 17:14:10 +00:00
|
|
|
1 + 64
|
2017-02-09 15:26:15 +00:00
|
|
|
|
|
|
|
def print(self):
|
2017-02-09 17:14:10 +00:00
|
|
|
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'))
|
2017-04-02 00:25:55 +00:00
|
|
|
print(' * vstr :', self.vstr.decode('ascii'))
|
|
|
|
print(' * vimg : (%d bytes)' % len(self.vimg))
|
2017-04-06 16:57:51 +00:00
|
|
|
print(' * sigmask :', format_sigmask(self.sigmask))
|
2017-04-02 00:25:55 +00:00
|
|
|
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
|
2017-02-09 17:14:10 +00:00
|
|
|
|
2017-03-30 20:58:00 +00:00
|
|
|
def serialize_header(self, sig=True):
|
2017-09-05 21:15:47 +00:00
|
|
|
header = struct.pack('<4sIIBBBB',
|
|
|
|
self.magic, self.hdrlen, self.expiry,
|
|
|
|
self.vmajor, self.vminor,
|
|
|
|
self.vsig_m, self.vsig_n)
|
2017-02-09 17:14:10 +00:00
|
|
|
for i in range(self.vsig_n):
|
|
|
|
header += self.vpub[i]
|
|
|
|
header += struct.pack('<B', self.vstr_len) + self.vstr
|
2017-09-05 21:15:47 +00:00
|
|
|
header += (-len(header) & 3) * b'\x00' # vstr_pad
|
2017-04-02 00:25:55 +00:00
|
|
|
header += self.vimg
|
2017-02-09 17:14:10 +00:00
|
|
|
if sig:
|
2017-04-01 00:32:05 +00:00
|
|
|
header += struct.pack('<B64s', self.sigmask, self.sig)
|
2017-02-09 17:14:10 +00:00
|
|
|
else:
|
|
|
|
header += 65 * b'\x00'
|
2017-02-17 16:11:34 +00:00
|
|
|
assert len(header) == self.hdrlen
|
2017-02-09 17:14:10 +00:00
|
|
|
return header
|
2017-02-09 15:26:15 +00:00
|
|
|
|
2017-04-08 16:23:08 +00:00
|
|
|
def sign(self, sigmask, seckey):
|
2017-03-30 20:58:00 +00:00
|
|
|
header = self.serialize_header(sig=False)
|
2017-04-08 16:23:08 +00:00
|
|
|
self.sigmask = sigmask
|
|
|
|
self.sig = sign_data(seckey, header)
|
2017-02-09 17:14:10 +00:00
|
|
|
|
|
|
|
def write(self, filename):
|
|
|
|
with open(filename, 'wb') as f:
|
2017-03-30 20:58:00 +00:00
|
|
|
f.write(self.serialize_header())
|
2017-02-09 15:26:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
def binopen(filename):
|
|
|
|
data = open(filename, 'rb').read()
|
2017-08-07 09:50:12 +00:00
|
|
|
data = bytearray(data) # python2/3 compatibility
|
2017-02-09 15:26:15 +00:00
|
|
|
magic = data[:4]
|
2017-04-10 17:24:21 +00:00
|
|
|
if magic == b'TRZB':
|
|
|
|
return BootloaderImage(data)
|
2017-04-01 13:45:50 +00:00
|
|
|
if magic == b'TRZV':
|
|
|
|
vheader = VendorHeader(data)
|
|
|
|
if len(data) == vheader.hdrlen:
|
|
|
|
return vheader
|
|
|
|
subdata = data[vheader.hdrlen:]
|
|
|
|
if subdata[:4] == b'TRZF':
|
|
|
|
return FirmwareImage(data, vheader.hdrlen)
|
2017-02-09 15:26:15 +00:00
|
|
|
if magic == b'TRZF':
|
2017-04-01 13:45:50 +00:00
|
|
|
return FirmwareImage(data, 0)
|
2017-02-09 15:26:15 +00:00
|
|
|
raise Exception('Unknown file format')
|
|
|
|
|
2017-09-05 21:15:47 +00:00
|
|
|
|
2017-02-09 15:26:15 +00:00
|
|
|
def main():
|
|
|
|
if len(sys.argv) < 2:
|
2017-04-08 16:23:08 +00:00
|
|
|
print('Usage: binctl file.bin [-s index seckey]')
|
2017-02-09 15:26:15 +00:00
|
|
|
return 1
|
|
|
|
fn = sys.argv[1]
|
|
|
|
sign = len(sys.argv) > 2 and sys.argv[2] == '-s'
|
|
|
|
b = binopen(fn)
|
|
|
|
if sign:
|
2017-04-08 16:23:08 +00:00
|
|
|
sigmask = 1 << (int(sys.argv[3]) - 1)
|
|
|
|
seckey = binascii.unhexlify(sys.argv[4])
|
|
|
|
b.sign(sigmask, seckey)
|
2017-02-09 15:26:15 +00:00
|
|
|
print()
|
|
|
|
b.write(fn)
|
2017-04-08 16:23:08 +00:00
|
|
|
b.print()
|
2017-02-09 15:26:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|