mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-17 01:52:02 +00:00
tools: join check_bootloader and check_firmware into firmwarectl tool
This commit is contained in:
parent
a8f5738398
commit
7d4414000e
@ -22,6 +22,7 @@ it will start in a firmware update mode, allowing a firmware update via USB.
|
|||||||
|
|
||||||
* Hash function used below is SHA-256 and signature system is Ed25519 (allows combining signatures by multiple keys into one).
|
* Hash function used below is SHA-256 and signature system is Ed25519 (allows combining signatures by multiple keys into one).
|
||||||
* All multibyte integer values are little endian.
|
* All multibyte integer values are little endian.
|
||||||
|
* There is a tool called [firmwarectl](../tools/firmwarectl) which checks validity of the bootloader/firmware images including their headers.
|
||||||
|
|
||||||
##Bootloader Format
|
##Bootloader Format
|
||||||
|
|
||||||
@ -30,11 +31,10 @@ TREZOR Core (second stage) bootloader consists of 2 parts:
|
|||||||
1. bootloader header
|
1. bootloader header
|
||||||
2. bootloader code
|
2. bootloader code
|
||||||
|
|
||||||
There is a tool called [check_bootloader](../tools/check_bootloader) which parses and checks validity of the bootloader including its header.
|
|
||||||
|
|
||||||
###Bootloader Header
|
###Bootloader Header
|
||||||
|
|
||||||
Total length of bootloader header is 256 bytes.
|
Total length of bootloader header is always 256 bytes.
|
||||||
|
|
||||||
| offset | length | name | description |
|
| offset | length | name | description |
|
||||||
|-------:|-------:|------|-------------|
|
|-------:|-------:|------|-------------|
|
||||||
@ -46,9 +46,9 @@ Total length of bootloader header is 256 bytes.
|
|||||||
| 0x0011 | 1 | vminor | version (minor) |
|
| 0x0011 | 1 | vminor | version (minor) |
|
||||||
| 0x0012 | 1 | vpatch | version (patch) |
|
| 0x0012 | 1 | vpatch | version (patch) |
|
||||||
| 0x0013 | 1 | vbuild | version (build) |
|
| 0x0013 | 1 | vbuild | version (build) |
|
||||||
| 0x0014 | 1 | sigidx | SatoshiLabs signature indexes (bitmap) |
|
| 0x0014 | 171 | reserved | not used yet (zeroed) |
|
||||||
| 0x0015 | 64 | sig | SatoshiLabs signature |
|
| 0x00BF | 1 | sigidx | SatoshiLabs signature indexes (bitmap) |
|
||||||
| 0x0055 | 171 | reserved | not used yet (zeroed) |
|
| 0x00C0 | 64 | sig | SatoshiLabs signature |
|
||||||
|
|
||||||
##Firmware Format
|
##Firmware Format
|
||||||
|
|
||||||
@ -58,8 +58,6 @@ TREZOR Core firmware consists of 3 parts:
|
|||||||
2. firmware header
|
2. firmware header
|
||||||
3. firmware code
|
3. firmware code
|
||||||
|
|
||||||
There is a tool called [check_firmware](../tools/check_firmware) which parses and checks validity of the firmware including the both headers.
|
|
||||||
|
|
||||||
###Vendor Header
|
###Vendor Header
|
||||||
|
|
||||||
Total length of vendor header is 82 + 32 * (number of pubkeys) + (length of vendor string) + (length of vendor image) bytes rounded up to the closest multiply of 256 bytes.
|
Total length of vendor header is 82 + 32 * (number of pubkeys) + (length of vendor string) + (length of vendor image) bytes rounded up to the closest multiply of 256 bytes.
|
||||||
@ -69,21 +67,23 @@ Total length of vendor header is 82 + 32 * (number of pubkeys) + (length of vend
|
|||||||
| 0x0000 | 4 | magic | firmware magic `TRZV` |
|
| 0x0000 | 4 | magic | firmware magic `TRZV` |
|
||||||
| 0x0004 | 4 | hdrlen | length of the vendor header |
|
| 0x0004 | 4 | hdrlen | length of the vendor header |
|
||||||
| 0x0008 | 4 | expiry | valid until timestamp (0=infinity) |
|
| 0x0008 | 4 | expiry | valid until timestamp (0=infinity) |
|
||||||
| 0x000C | 1 | vsig_m | number of signatures needed to run the firmware from this vendor |
|
| 0x000C | 1 | vmajor | version (major) |
|
||||||
| 0x000D | 1 | vsig_n | number of pubkeys vendor wants to use for signing |
|
| 0x000D | 1 | vminor | version (minor) |
|
||||||
| 0x000E | 2 | reserved | not used yet (zeroed) |
|
| 0x000E | 1 | vsig_m | number of signatures needed to run the firmware from this vendor |
|
||||||
|
| 0x000F | 1 | vsig_n | number of different pubkeys vendor provides for signing |
|
||||||
| 0x0010 | 32 | vpub1 | vendor pubkey 1 |
|
| 0x0010 | 32 | vpub1 | vendor pubkey 1 |
|
||||||
| ... | ... | ... | ... |
|
| ... | ... | ... | ... |
|
||||||
| ? | 32 | vpubn | vendor pubkey n |
|
| ? | 32 | vpubn | vendor pubkey n |
|
||||||
| ? | 1 | vstr_len | vendor string length |
|
| ? | 1 | vstr_len | vendor string length |
|
||||||
| ? | ? | vstr | vendor string |
|
| ? | ? | vstr | vendor string |
|
||||||
|
| ? | 2 | vimg_len | vendor image length |
|
||||||
| ? | ? | vimg | vendor image (in [TOIf format](toif.md)) |
|
| ? | ? | vimg | vendor image (in [TOIf format](toif.md)) |
|
||||||
| ? | 1 | sigidx | SatoshiLabs signature indexes (bitmap) |
|
| ? | 1 | sigidx | SatoshiLabs signature indexes (bitmap) |
|
||||||
| ? | 64 | sig | SatoshiLabs signature |
|
| ? | 64 | sig | SatoshiLabs signature |
|
||||||
|
|
||||||
###Firmware Header
|
###Firmware Header
|
||||||
|
|
||||||
Total length of firmware header is 256 bytes.
|
Total length of firmware header is always 256 bytes.
|
||||||
|
|
||||||
| offset | length | name | description |
|
| offset | length | name | description |
|
||||||
|-------:|-------:|------|-------------|
|
|-------:|-------:|------|-------------|
|
||||||
@ -95,9 +95,9 @@ Total length of firmware header is 256 bytes.
|
|||||||
| 0x0011 | 1 | vminor | version (minor) |
|
| 0x0011 | 1 | vminor | version (minor) |
|
||||||
| 0x0012 | 1 | vpatch | version (patch) |
|
| 0x0012 | 1 | vpatch | version (patch) |
|
||||||
| 0x0013 | 1 | vbuild | version (build) |
|
| 0x0013 | 1 | vbuild | version (build) |
|
||||||
| 0x0014 | 1 | sigidx | vendor signature indexes (bitmap) |
|
| 0x0014 | 171 | reserved | not used yet (zeroed) |
|
||||||
| 0x0015 | 64 | sig | vendor signature |
|
| 0x00BF | 1 | sigidx | vendor signature indexes (bitmap) |
|
||||||
| 0x0055 | 171 | reserved | not used yet (zeroed) |
|
| 0x00C0 | 64 | sig | vendor signature |
|
||||||
|
|
||||||
##Various ideas
|
##Various ideas
|
||||||
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import struct
|
|
||||||
|
|
||||||
# bootloader header specification: https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md#bootloader-header
|
|
||||||
|
|
||||||
def check_bootloader(filename):
|
|
||||||
data = open(filename, 'rb').read()
|
|
||||||
hdr = struct.unpack('<4sIIIBBBBB64s171s', data[:256])
|
|
||||||
code = data[256:]
|
|
||||||
magic, hdrlen, expiry, codelen, vmajor, vminor, vpatch, vbuild, sigidx, sig, reserved = hdr
|
|
||||||
sigidx = tuple([ i + 1 for i in range(8) if sigidx & (1 << i) ])
|
|
||||||
print('magic :', magic)
|
|
||||||
assert magic == b'TRZB'
|
|
||||||
print('hdrlen :', hdrlen)
|
|
||||||
assert hdrlen == 256
|
|
||||||
print('expiry :', expiry)
|
|
||||||
print('codelen :', codelen)
|
|
||||||
print('vmajor :', vmajor)
|
|
||||||
print('vminor :', vminor)
|
|
||||||
print('vpatch :', vpatch)
|
|
||||||
print('vbuild :', vbuild)
|
|
||||||
print('sigidx :', sigidx)
|
|
||||||
print('sig :', sig)
|
|
||||||
assert reserved == 171 * b'\x00'
|
|
||||||
assert codelen == 64*1024
|
|
||||||
assert codelen == len(code)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print('Usage: check_bootloader bootloader.bin')
|
|
||||||
return 1
|
|
||||||
fn = sys.argv[1]
|
|
||||||
check_bootloader(fn)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def process_firmware(filename):
|
|
||||||
data = open(filename, 'rb').read()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print('Usage: check_firmware firmware.bin')
|
|
||||||
return 1
|
|
||||||
fn = sys.argv[1]
|
|
||||||
process_firmware(fn)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
139
tools/firmwarectl
Executable file
139
tools/firmwarectl
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import binascii
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
import ed25519
|
||||||
|
|
||||||
|
# bootloader/firmware headers specification: https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md
|
||||||
|
|
||||||
|
# converts 8-bit bitmap to tuple of values
|
||||||
|
def bitmap_to_tuple(b):
|
||||||
|
return tuple([ i + 1 for i in range(8) if b & (1 << i) ])
|
||||||
|
|
||||||
|
|
||||||
|
# converts tuple of values to 8-bit bitmap
|
||||||
|
def tuple_to_bitmap(t):
|
||||||
|
b = 0
|
||||||
|
for i in t:
|
||||||
|
if i >= 1 and i <= 8:
|
||||||
|
b |= (1 << (i - 1))
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
|
def get_sig(data):
|
||||||
|
print('Enter privkey: ', end='')
|
||||||
|
seckey = binascii.unhexlify(input())
|
||||||
|
signkey = ed25519.SigningKey(seckey)
|
||||||
|
digest = hashlib.sha256(data).digest()
|
||||||
|
sigidx = (1, )
|
||||||
|
sig = signkey.sign(digest)
|
||||||
|
return sigidx, sig
|
||||||
|
|
||||||
|
|
||||||
|
class BootloaderImage:
|
||||||
|
|
||||||
|
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'TRZB'
|
||||||
|
assert self.hdrlen == 256
|
||||||
|
assert self.codelen == 64*1024
|
||||||
|
assert self.reserved == 171 * b'\x00'
|
||||||
|
self.sigidx = bitmap_to_tuple(self.sigidx)
|
||||||
|
self.code = data[256:]
|
||||||
|
assert len(self.code) == self.codelen
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
print('TREZOR Bootloader 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 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:
|
||||||
|
sigidx = tuple_to_bitmap(self.sigidx)
|
||||||
|
header += struct.pack('<B64s', sigidx, self.sig)
|
||||||
|
else:
|
||||||
|
header += 65 * b'\x00'
|
||||||
|
return header
|
||||||
|
|
||||||
|
def sign(self):
|
||||||
|
header = self.header(sig=False)
|
||||||
|
data = header + self.code
|
||||||
|
self.sigidx, self.sig = get_sig(data)
|
||||||
|
|
||||||
|
def write(self, filename):
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(self.header())
|
||||||
|
f.write(self.code)
|
||||||
|
|
||||||
|
|
||||||
|
class VendorHeader:
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sign(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FirmwareImage:
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sign(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def binopen(filename):
|
||||||
|
print()
|
||||||
|
print('Opening file', filename)
|
||||||
|
print()
|
||||||
|
data = open(filename, 'rb').read()
|
||||||
|
magic = data[:4]
|
||||||
|
if magic == b'TRZB':
|
||||||
|
return BootloaderImage(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()
|
Loading…
Reference in New Issue
Block a user