1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-27 00:28:10 +00:00

build: refactor binctl and firmware/loader image stuff

This commit is contained in:
Pavol Rusnak 2017-04-01 02:32:05 +02:00
parent e37619899e
commit 435e96e1b3
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D
12 changed files with 265 additions and 156 deletions

View File

@ -56,6 +56,7 @@ OBJ_HAL += $(addprefix $(BUILD_MP)/,\
# OBJ micropython/
OBJ_FW += $(addprefix $(BUILD_FW)/, \
loader/crypto.o \
loader/header.o \
loader/main.o \
extmod/modtrezorui/display.o \

View File

@ -48,8 +48,8 @@ Total length of loader header is always 512 bytes.
| 0x0012 | 1 | vpatch | version (patch) |
| 0x0013 | 1 | vbuild | version (build) |
| 0x0014 | 427 | reserved | not used yet (zeroed) |
| 0x01BF | 1 | sigidx | SatoshiLabs signature indexes (bitmap) |
| 0x01C0 | 64 | sig | SatoshiLabs signature |
| 0x01BF | 1 | sigmask | SatoshiLabs signature indexes (bitmap) |
| 0x01C0 | 64 | sig | SatoshiLabs aggregated signature |
## Firmware Format
@ -79,8 +79,8 @@ Total length of vendor header is 84 + 32 * (number of pubkeys) + (length of vend
| ? | ? | vstr | vendor string |
| ? | 2 | vimg_len | vendor image length |
| ? | ? | vimg | vendor image (in [TOIf format](toif.md)) |
| ? | 1 | sigidx | SatoshiLabs signature indexes (bitmap) |
| ? | 64 | sig | SatoshiLabs signature |
| ? | 1 | sigmask | SatoshiLabs signature indexes (bitmap) |
| ? | 64 | sig | SatoshiLabs aggregated signature |
### Firmware Header
@ -97,8 +97,8 @@ Total length of firmware header is always 512 bytes.
| 0x0012 | 1 | vpatch | version (patch) |
| 0x0013 | 1 | vbuild | version (build) |
| 0x0014 | 427 | reserved | not used yet (zeroed) |
| 0x01BF | 1 | sigidx | vendor signature indexes (bitmap) |
| 0x01C0 | 64 | sig | vendor signature |
| 0x01BF | 1 | sigmask | vendor signature indexes (bitmap) |
| 0x01C0 | 64 | sig | vendor aggregated signature |
## Various ideas

View File

@ -3,9 +3,10 @@
#include "blake2s.h"
#include "ed25519-donna/ed25519.h"
#include "common.h"
#include "crypto.h"
bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigidx, uint8_t *sig)
bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigmask, uint8_t *sig)
{
uint32_t magic;
memcpy(&magic, data, 4);
@ -13,7 +14,7 @@ bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigidx, uint8
uint32_t hdrlen;
memcpy(&hdrlen, data + 4, 4);
if (hdrlen != 256) return false;
if (hdrlen != HEADER_SIZE) return false;
uint32_t expiry;
memcpy(&expiry, data + 8, 4);
@ -21,7 +22,6 @@ bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigidx, uint8
uint32_t clen;
memcpy(&clen, data + 12, 4);
// stage 2 (+header) must fit into sectors 4...11 - see docs/memory.md for more info
if (clen + hdrlen < 4 * 1024) return false;
if (clen + hdrlen > 64 * 1024 + 7 * 128 * 1024) return false;
if ((clen + hdrlen) % 512 != 0) return false;
@ -33,14 +33,14 @@ bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigidx, uint8
uint32_t version;
memcpy(&version, data + 16, 4);
// uint8_t reserved[171];
// uint8_t reserved[427];
if (sigidx) {
memcpy(sigidx, data + 0x00BF, 1);
if (sigmask) {
memcpy(sigmask, data + 0x01BF, 1);
}
if (sig) {
memcpy(sig, data + 0x00C0, 64);
memcpy(sig, data + 0x01C0, 64);
}
return true;
@ -79,9 +79,9 @@ const uint8_t *get_pubkey(uint8_t index)
bool check_signature(const uint8_t *start)
{
uint32_t codelen;
uint8_t sigidx;
uint8_t sigmask;
uint8_t sig[64];
if (!parse_header(start, &codelen, &sigidx, sig)) {
if (!parse_header(start, &codelen, &sigmask, sig)) {
return false;
}
@ -95,7 +95,11 @@ bool check_signature(const uint8_t *start)
blake2s_Update(&ctx, start + 256, codelen);
blake2s_Final(&ctx, hash, BLAKE2S_DIGEST_LENGTH);
const uint8_t *pub = get_pubkey(sigidx);
const uint8_t *pub = get_pubkey(sigmask);
// TODO: remove debug skip of unsigned
if (!pub) return true;
// end
return pub && (0 == ed25519_sign_open(hash, BLAKE2S_DIGEST_LENGTH, *(const ed25519_public_key *)pub, *(const ed25519_signature *)sig));
}

View File

@ -112,6 +112,27 @@ bool copy_sdcard(void)
return true;
}
void check_and_jump(void)
{
BOOTLOADER_PRINTLN("checking loader");
if (parse_header((const uint8_t *)LOADER_START, NULL, NULL, NULL)) {
BOOTLOADER_PRINTLN("valid loader header");
if (check_signature((const uint8_t *)LOADER_START)) {
BOOTLOADER_PRINTLN("valid loader signature");
// TODO: remove debug wait
BOOTLOADER_PRINTLN("waiting 1 second");
HAL_Delay(1000);
// end
BOOTLOADER_PRINTLN("JUMP!");
jump_to(LOADER_START + HEADER_SIZE);
} else {
BOOTLOADER_PRINTLN("invalid loader signature");
}
} else {
BOOTLOADER_PRINTLN("invalid loader header");
}
}
int main(void)
{
SCB->VTOR = BOOTLOADER_START;
@ -127,32 +148,13 @@ int main(void)
BOOTLOADER_PRINTLN("=================");
BOOTLOADER_PRINTLN("starting bootloader");
// TODO: remove debug
BOOTLOADER_PRINTLN("waiting 1 second");
HAL_Delay(1000);
BOOTLOADER_PRINTLN("jumping to loader");
jump_to(LOADER_START + HEADER_SIZE);
// end
if (check_sdcard()) {
if (!copy_sdcard()) {
__fatal_error("halt");
}
}
BOOTLOADER_PRINTLN("checking loader");
if (parse_header((const uint8_t *)LOADER_START, NULL, NULL, NULL)) {
BOOTLOADER_PRINTLN("valid loader header");
if (check_signature((const uint8_t *)LOADER_START)) {
BOOTLOADER_PRINTLN("valid loader signature");
BOOTLOADER_PRINTLN("JUMP!");
jump_to(LOADER_START + HEADER_SIZE);
} else {
BOOTLOADER_PRINTLN("invalid loader signature");
}
} else {
BOOTLOADER_PRINTLN("invalid loader header");
}
check_and_jump();
__fatal_error("halt");

View File

@ -1,20 +1,20 @@
#include "version.h"
.section .header,"a",%progbits
.section .header,"a",%progbits
.type g_header, %object
.size g_header, .-g_header
.type g_header, %object
.size g_header, .-g_header
g_header:
.byte 'T','R','Z','F'
.word g_header_end - g_header
.word 0 /* valid until */
.word _codelen
.byte VERSION_MAJOR
.byte VERSION_MINOR
.byte VERSION_PATCH
.byte VERSION_BUILD
. = . + 427 /* reserved */
.byte 0 /* sigindex */
. = . + 64 /* signatures */
.byte 'T','R','Z','F' // magic
.word g_header_end - g_header // hdrlen
.word 0 // expiry
.word _codelen // codelen
.byte VERSION_MAJOR // vmajor
.byte VERSION_MINOR // vminor
.byte VERSION_PATCH // vpatch
.byte VERSION_BUILD // vbuild
. = . + 427 // reserved
.byte 0 // sigmask
. = . + 64 // sig
g_header_end:

View File

@ -34,7 +34,7 @@ SECTIONS
. = ALIGN(4);
*(.text*) /* .text* sections (code) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
. = ALIGN(512);
_etext = .; /* define a global symbol at end of code */
} >FLASH
@ -51,7 +51,7 @@ SECTIONS
_sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */
*(.data*) /* .data* sections */
. = ALIGN(4);
. = ALIGN(512);
_edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */
} >RAM AT> FLASH
@ -87,7 +87,7 @@ SECTIONS
}
/* RAM extents for the garbage collector */
_codelen = SIZEOF(.flash) + SIZEOF(.data);
_codelen = SIZEOF(.flash) + SIZEOF(.data) - 512;
_ram_start = ORIGIN(RAM);
_ram_end = ORIGIN(RAM) + LENGTH(RAM);
_heap_start = _ebss; /* heap starts just after statically allocated memory */

105
micropython/loader/crypto.c Normal file
View File

@ -0,0 +1,105 @@
#include <string.h>
#include "blake2s.h"
#include "ed25519-donna/ed25519.h"
#include "common.h"
#include "crypto.h"
bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigmask, uint8_t *sig)
{
uint32_t magic;
memcpy(&magic, data, 4);
if (magic != 0x465A5254) return false; // TRZF
uint32_t hdrlen;
memcpy(&hdrlen, data + 4, 4);
if (hdrlen != HEADER_SIZE) return false;
uint32_t expiry;
memcpy(&expiry, data + 8, 4);
if (expiry != 0) return false;
uint32_t clen;
memcpy(&clen, data + 12, 4);
if (clen + hdrlen < 4 * 1024) return false;
if (clen + hdrlen > 7 * 128 * 1024) return false;
if ((clen + hdrlen) % 512 != 0) return false;
if (codelen) {
*codelen = clen;
}
uint32_t version;
memcpy(&version, data + 16, 4);
// uint8_t reserved[427];
if (sigmask) {
memcpy(sigmask, data + 0x01BF, 1);
}
if (sig) {
memcpy(sig, data + 0x01C0, 64);
}
return true;
}
#define KEYMASK(A, B, C) ((1 << (A - 1)) | (1 << (B - 1)) | (1 << (C - 1)))
const uint8_t *get_pubkey(uint8_t index)
{
switch (index) {
case KEYMASK(1, 2, 3):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(1, 2, 4):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(1, 2, 5):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(1, 3, 4):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(1, 3, 5):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(1, 4, 5):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(2, 3, 4):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(2, 3, 5):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(2, 4, 5):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
case KEYMASK(3, 4, 5):
return (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
default:
return NULL;
}
}
bool check_signature(const uint8_t *start)
{
uint32_t codelen;
uint8_t sigmask;
uint8_t sig[64];
if (!parse_header(start, &codelen, &sigmask, sig)) {
return false;
}
uint8_t hash[BLAKE2S_DIGEST_LENGTH];
BLAKE2S_CTX ctx;
blake2s_Init(&ctx, BLAKE2S_DIGEST_LENGTH);
blake2s_Update(&ctx, start, 256 - 65);
for (int i = 0; i < 65; i++) {
blake2s_Update(&ctx, (const uint8_t *)"\x00", 1);
}
blake2s_Update(&ctx, start + 256, codelen);
blake2s_Final(&ctx, hash, BLAKE2S_DIGEST_LENGTH);
const uint8_t *pub = get_pubkey(sigmask);
// TODO: remove debug skip of unsigned
if (!pub) return true;
// end
return pub && (0 == ed25519_sign_open(hash, BLAKE2S_DIGEST_LENGTH, *(const ed25519_public_key *)pub, *(const ed25519_signature *)sig));
}

View File

@ -0,0 +1,11 @@
#ifndef __LOADER_CRYPTO_H__
#define __LOADER_CRYPTO_H__
#include <stdint.h>
#include <stdbool.h>
bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigidx, uint8_t *sig);
bool check_signature(const uint8_t *start);
#endif

View File

@ -1,20 +1,20 @@
#include "version.h"
.section .header,"a",%progbits
.section .header,"a",%progbits
.type g_header, %object
.size g_header, .-g_header
.type g_header, %object
.size g_header, .-g_header
g_header:
.byte 'T','R','Z','L'
.word g_header_end - g_header
.word 0 /* valid until */
.word _codelen
.byte VERSION_MAJOR
.byte VERSION_MINOR
.byte VERSION_PATCH
.byte VERSION_BUILD
. = . + 427 /* reserved */
.byte 0 /* sigindex */
. = . + 64 /* signatures */
.byte 'T','R','Z','L' // magic
.word g_header_end - g_header // hdrlen
.word 0 // expiry
.word _codelen // codelen
.byte VERSION_MAJOR // vmajor
.byte VERSION_MINOR // vminor
.byte VERSION_PATCH // vpatch
.byte VERSION_BUILD // vbuild
. = . + 427 // reserved
.byte 0 // sigmask
. = . + 64 // sig
g_header_end:

View File

@ -2,6 +2,7 @@
#include "common.h"
#include "display.h"
#include "crypto.h"
#define LOADER_FGCOLOR 0xFFFF
#define LOADER_BGCOLOR 0x0000
@ -13,6 +14,27 @@ void pendsv_isr_handler(void) {
__fatal_error("pendsv");
}
void check_and_jump(void)
{
LOADER_PRINTLN("checking firmware");
if (parse_header((const uint8_t *)FIRMWARE_START, NULL, NULL, NULL)) {
LOADER_PRINTLN("valid firmware header");
if (check_signature((const uint8_t *)FIRMWARE_START)) {
LOADER_PRINTLN("valid firmware signature");
LOADER_PRINTLN("JUMP!");
// TODO: remove debug wait
LOADER_PRINTLN("waiting 1 second");
HAL_Delay(1000);
// end
jump_to(FIRMWARE_START + HEADER_SIZE);
} else {
LOADER_PRINTLN("invalid firmware signature");
}
} else {
LOADER_PRINTLN("invalid firmware header");
}
}
int main(void)
{
SCB->VTOR = LOADER_START + HEADER_SIZE;
@ -22,12 +44,13 @@ int main(void)
display_clear();
display_backlight(255);
LOADER_PRINTLN("reached loader");
LOADER_PRINTLN("waiting 1 second");
HAL_Delay(1000);
LOADER_PRINTLN("jumping to firmware");
LOADER_PRINTLN("TREZOR Loader");
LOADER_PRINTLN("=============");
LOADER_PRINTLN("starting loader");
jump_to(FIRMWARE_START + HEADER_SIZE);
check_and_jump();
__fatal_error("halt");
return 0;
}

View File

@ -29,12 +29,12 @@ SECTIONS
.flash :
{
. = ALIGN(4);
KEEP(*(.header))
KEEP(*(.header)) /* Firmware Header */
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
*(.text*) /* .text* sections (code) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
. = ALIGN(512);
_etext = .; /* define a global symbol at end of code */
} >FLASH
@ -51,9 +51,8 @@ SECTIONS
_sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */
*(.data*) /* .data* sections */
. = ALIGN(4);
. = ALIGN(512);
_edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */
_datalen = . - _sdata;
} >RAM AT> FLASH
/* Uninitialized data section */
@ -88,7 +87,7 @@ SECTIONS
}
/* RAM extents for the garbage collector */
_codelen = SIZEOF(.flash) + SIZEOF(.data);
_codelen = SIZEOF(.flash) + SIZEOF(.data) - 512;
_ram_start = ORIGIN(RAM);
_ram_end = ORIGIN(RAM) + LENGTH(RAM);
_heap_start = _ebss; /* heap starts just after statically allocated memory */

View File

@ -6,22 +6,22 @@ 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 _ _ _ _)
sigmask = 0x01 # (1 _ _ _ _)
sig = signkey.sign(digest)
return sigidx, sig
return sigmask, sig
class LoaderImage:
# loader/firmware headers specification: https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md
def __init__(self, data):
class BinImage:
def __init__(self, data, magic, max_size):
header = struct.unpack('<4sIIIBBBB427sB64s', data[:512])
self.magic, \
self.hdrlen, \
@ -32,25 +32,31 @@ class LoaderImage:
self.vpatch, \
self.vbuild, \
self.reserved, \
self.sigidx, \
self.sigmask, \
self.sig = header
assert self.magic == b'TRZL'
assert self.magic == magic
assert self.hdrlen == 512
assert self.codelen + self.hdrlen >= 4 * 1024
assert self.codelen + self.hdrlen <= 64 * 1024 + 7 * 128 * 1024
assert (self.codelen + self.hdrlen) % 512 == 0
total_len = self.hdrlen + self.codelen
assert total_len % 512 == 0
assert total_len >= 4 * 1024
assert total_len <= max_size
assert self.reserved == 427 * b'\x00'
self.code = data[self.hdrlen:]
assert len(self.code) == self.codelen
def print(self):
print('TREZOR Loader Image')
if self.magic == b'TRZF':
print('TREZOR Firmware Image')
elif self.magic == b'TRZL':
print('TREZOR Loader Image')
else:
print('TREZOR Unknown 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(' * sigmask :', self.sigmask)
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
def serialize_header(self, sig=True):
@ -59,7 +65,7 @@ class LoaderImage:
self.vmajor, self.vminor, self.vpatch, self.vbuild, \
self.reserved)
if sig:
header += struct.pack('<B64s', self.sigidx, self.sig)
header += struct.pack('<B64s', self.sigmask, self.sig)
else:
header += 65 * b'\x00'
assert len(header) == self.hdrlen
@ -69,7 +75,7 @@ class LoaderImage:
header = self.serialize_header(sig=False)
data = header + self.code
assert len(data) == self.hdrlen + self.codelen
self.sigidx, self.sig = get_sig(data)
self.sigmask, self.sig = get_sig(data)
def write(self, filename):
with open(filename, 'wb') as f:
@ -77,6 +83,19 @@ class LoaderImage:
f.write(self.code)
class FirmwareImage(BinImage):
def __init__(self, data):
super().__init__(data, magic=b'TRZF', max_size=7*128*1024)
class LoaderImage(BinImage):
def __init__(self, data):
super().__init__(data, magic=b'TRZL', max_size=64*1024 + 7*128*1024)
"""
class VendorHeader:
def __init__(self, data):
@ -104,7 +123,7 @@ class VendorHeader:
p += 2
self.vimg = data[p:p + self.vimg_len]
p += self.vimg_len
self.sigidx = data[p]
self.sigmask = data[p]
p += 1
self.sig = data[p:p+64]
assert len(data) == 4 + 4 + 4 + 1 + 1 + 1 + 1 + \
@ -124,8 +143,8 @@ class VendorHeader:
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'))
print(' * sigmask :', self.sigmask)
print(' * sig :', binascii.hexlify(self.sig).decode('ascii'))
def serialize_header(self, sig=True):
header = struct.pack('<4sIIBBBB', \
@ -137,7 +156,7 @@ class VendorHeader:
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)
header += struct.pack('<B64s', self.sigmask, self.sig)
else:
header += 65 * b'\x00'
assert len(header) == self.hdrlen
@ -145,67 +164,12 @@ class VendorHeader:
def sign(self):
header = self.serialize_header(sig=False)
self.sigidx, self.sig = get_sig(header)
self.sigmask, 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('<4sIIIBBBB427sB64s', data[:512])
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 == 512
assert self.codelen % 4 == 0
assert self.reserved == 427 * 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('<4sIIIBBBB427s', \
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):
@ -216,10 +180,10 @@ def binopen(filename):
magic = data[:4]
if magic == b'TRZL':
return LoaderImage(data)
if magic == b'TRZV':
return VendorHeader(data)
if magic == b'TRZF':
return FirmwareImage(data)
# if magic == b'TRZV':
# return VendorHeader(data)
raise Exception('Unknown file format')