Implemented vendor header.

Header is generated with

    ./tools/build_vendorheader 'key1,key2,key3' 2 1.1 SatoshiLabs assets/satoshilabs.png micropython/firmware/vendorheader.bin

where

- keyN is a 64 character hex string encoding the public key
- 2 encodes 2/3 key scheme
- 1.1 is the version number (major, minor)
- SatoshiLabs is the vendor name
- satoshilabs.png is the vendor image

Updated the firmware compilation that it adds vendor header and updated loader
that it handles vendor header to be present.
This commit is contained in:
Jochen Hoenicke 2017-04-01 15:45:50 +02:00
parent 300daa4513
commit e0fd890661
9 changed files with 148 additions and 21 deletions

View File

@ -311,6 +311,7 @@ OBJ_HAL += $(addprefix $(BUILD_MP)/,\
# OBJ micropython/
OBJ_FW += $(addprefix $(BUILD_FW)/, \
firmware/vendorheader.o \
firmware/header.o \
firmware/main.o \
firmware/mphalport.o \
@ -399,6 +400,11 @@ QSTR_GEN_EXTRA_CFLAGS += -DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB -DN_ARM -DN_XTENSA
all: $(BUILD)/$(TARGET).bin
$(BUILD_FW)/firmware/vendorheader.o: $(SRCDIR_FW)/firmware/vendorheader.bin
$(Q)$(OBJCOPY) -I binary -O elf32-littlearm -B arm \
--rename-section .data=.vendorheader,alloc,load,readonly,contents \
$< $@
$(BUILD)/$(TARGET).elf: $(OBJ)
$(ECHO) "LINK $@"
$(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)

View File

@ -61,12 +61,12 @@ TREZOR Core firmware consists of 3 parts:
### Vendor Header
Total length of vendor header is 84 + 32 * (number of pubkeys) + (length of vendor string) + (length of vendor image) bytes rounded up to the closest multiply of 512 bytes.
Total length of vendor header is 84 + 32 * (number of pubkeys) + (length of vendor string rounded up to multiple of 4) + (length of vendor image) bytes rounded up to the closest multiple of 512 bytes.
| offset | length | name | description |
|-------:|-------:|------|-------------|
| 0x0000 | 4 | magic | firmware magic `TRZV` |
| 0x0004 | 4 | hdrlen | length of the vendor header |
| 0x0004 | 4 | hdrlen | length of the vendor header (multiple of 512) |
| 0x0008 | 4 | expiry | valid until timestamp (0=infinity) |
| 0x000C | 1 | vmajor | version (major) |
| 0x000D | 1 | vminor | version (minor) |
@ -77,8 +77,9 @@ Total length of vendor header is 84 + 32 * (number of pubkeys) + (length of vend
| ? | 32 | vpubn | vendor pubkey n |
| ? | 1 | vstr_len | vendor string length |
| ? | ? | vstr | vendor string |
| ? | 2 | vimg_len | vendor image length |
| ? | ? | vstrpad | padding to a multiple of 4 bytes |
| ? | ? | vimg | vendor image (in [TOIf format](toif.md)) |
| ? | ? | reserved | padding to an address that is -65 modulo 512 |
| ? | 1 | sigmask | SatoshiLabs signature indexes (bitmap) |
| ? | 64 | sig | SatoshiLabs aggregated signature |

View File

@ -1,2 +1,2 @@
#define IMAGE_MAGIC 0x4C5A5254 // TRZL
#define IMAGE_MAXSIZE (1 * 64 * 1024 + 7 * 128 + 1024)
#define IMAGE_MAXSIZE (1 * 64 * 1024 + 7 * 128 * 1024)

View File

@ -24,7 +24,6 @@
int main(void) {
SCB->VTOR = FIRMWARE_START + HEADER_SIZE;
periph_init();
pendsv_init();

View File

@ -28,6 +28,7 @@ SECTIONS
/* Firmware Header */
.header :
{
KEEP(*(.vendorheader))
KEEP(*(.header))
} > FLASH

View File

@ -1,2 +1,2 @@
#define IMAGE_MAGIC 0x465A5254 // TRZF
#define IMAGE_MAXSIZE (7 * 128 + 1024)
#define IMAGE_MAXSIZE (7 * 128 * 1024)

View File

@ -17,14 +17,16 @@ void pendsv_isr_handler(void) {
void check_and_jump(void)
{
LOADER_PRINTLN("checking firmware");
if (image_check_signature((const uint8_t *)FIRMWARE_START)) {
// TODO: check vendor header and its signature
uint32_t vhdrlen = *((const uint32_t *) (FIRMWARE_START + 4));
if (image_check_signature((const uint8_t *) (FIRMWARE_START + vhdrlen))) {
LOADER_PRINTLN("valid firmware image");
// TODO: remove debug wait
LOADER_PRINTLN("waiting 1 second");
HAL_Delay(1000);
// end
LOADER_PRINTLN("JUMP!");
jump_to(FIRMWARE_START + HEADER_SIZE);
jump_to(FIRMWARE_START + vhdrlen + HEADER_SIZE);
} else {
LOADER_PRINTLN("invalid firmware image");
}

View File

@ -85,9 +85,15 @@ class BinImage:
class FirmwareImage(BinImage):
def __init__(self, data):
super().__init__(data, magic=b'TRZF', max_size=7*128*1024)
def __init__(self, data, vhdrlen):
super().__init__(data[vhdrlen:], magic=b'TRZF', max_size=7*128*1024)
self.vheader = data[0:vhdrlen]
def write(self, filename):
with open(filename, 'wb') as f:
f.write(self.vheader)
f.write(self.serialize_header())
f.write(self.code)
class LoaderImage(BinImage):
@ -95,7 +101,6 @@ class LoaderImage(BinImage):
super().__init__(data, magic=b'TRZL', max_size=64*1024 + 7*128*1024)
"""
class VendorHeader:
def __init__(self, data):
@ -107,7 +112,7 @@ class VendorHeader:
self.vminor, \
self.vsig_m, \
self.vsig_n = header
assert self.magic == b'TRZF'
assert self.magic == b'TRZV'
assert self.vsig_m > 0 and self.vsig_m <= self.vsig_n
assert self.vsig_n > 0 and self.vsig_n <= 8
p = 16
@ -119,8 +124,9 @@ class VendorHeader:
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
vstr_pad = -p & 3
p += vstr_pad
self.vimg_len = len(data) - 65 - p
self.vimg = data[p:p + self.vimg_len]
p += self.vimg_len
self.sigmask = data[p]
@ -128,8 +134,8 @@ class VendorHeader:
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 + self.vstr_len + vstr_pad + \
self.vimg_len + \
1 + 64
def print(self):
@ -169,7 +175,6 @@ class VendorHeader:
def write(self, filename):
with open(filename, 'wb') as f:
f.write(self.serialize_header())
"""
def binopen(filename):
@ -180,13 +185,17 @@ def binopen(filename):
magic = data[:4]
if magic == b'TRZL':
return LoaderImage(data)
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)
if magic == b'TRZF':
return FirmwareImage(data)
# if magic == b'TRZV':
# return VendorHeader(data)
return FirmwareImage(data, 0)
raise Exception('Unknown file format')
def main():
if len(sys.argv) < 2:
print('Usage: binctl file.bin [-s]')

109
tools/build_vendorheader Executable file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
from PIL import Image
import sys
import re
import struct
import zlib
import binascii
def process_rgb(w, h, pix):
data = bytes()
for j in range(h):
for i in range(w):
r, g, b = pix[i, j]
c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
data += struct.pack('>H', c)
return data
def process_grayscale(w, h, pix):
data = bytes()
for j in range(h):
for i in range(w // 2):
l1, l2 = pix[i * 2, j], pix[i * 2 + 1, j]
c = (l1 & 0xF0) | (l2 >> 4)
data += struct.pack('>B', c)
return data
def process_image(ifn):
im = Image.open(ifn)
w, h = im.size
print('Opened %s ... %d x %d @ %s' % (ifn, w, h, im.mode))
if im.mode == 'RGB':
print('Detected RGB mode')
elif im.mode == 'L':
if w % 2 > 0:
print('PNG file must have width divisible by 2')
return 3
print('Detected GRAYSCALE mode')
else:
print('Unknown mode:', im.mode)
return 4
pix = im.load()
if im.mode == 'RGB':
ofn = '%s.toif' % ifn[:-4]
pixeldata = process_rgb(w, h, pix)
else:
ofn = '%s.toig' % ifn[:-4]
pixeldata = process_grayscale(w, h, pix)
z = zlib.compressobj(level=9, wbits=10)
zdata = z.compress(pixeldata) + z.flush()
zdata = zdata[2:-4] # strip header and checksum
toif = b''
if im.mode == 'RGB':
toif += b'TOIf'
else:
toif += b'TOIg'
toif += struct.pack('<HH', w, h)
toif += struct.pack('<I', len(zdata))
toif += zdata
return toif
# encode vendor name, add length byte and padding to multiple of 4
def vendorencode(vname):
vbin = vname.encode('utf-8')
vbin = struct.pack('<B',len(vbin)) + vbin
vbin += b'\0' * (-len(vbin) & 3)
return vbin
def encodekey(key):
if len(key) != 64:
raise Exception("Wrong key length")
return binascii.unhexlify(key)
def main():
if len(sys.argv) < 7:
print('Usage build_vendorheader key1hex,... m version vendorname vendorimage.png vendorimage.bin')
return 1
keys = list(map(encodekey, sys.argv[1].split(',')))
m = int(sys.argv[2])
(major,minor) = map(lambda x: int(x),
re.search('^(\d+)\.(\d+)$', sys.argv[3]).groups())
vname = sys.argv[4]
ifn = sys.argv[5]
ofn = sys.argv[6]
if not ifn.endswith('.png'):
print('Must provide PNG file')
return 2
timeout = 0
vheader = b'TRZV' + struct.pack('<IIBBBB', 0, timeout, major, minor, m, len(keys))
for k in keys:
vheader += k
vheader += vendorencode(vname) + process_image(ifn)
padding = 65 + (-len(vheader) - 65) & 511
vheader += b'\0' * padding
# put in length
vheader = vheader[0:4] + struct.pack('<I', len(vheader)) + vheader[8:]
with open(ofn, 'wb') as f:
f.write(vheader)
main()