diff --git a/Makefile.loader b/Makefile.loader index f67a6fef..ceb9a2a9 100644 --- a/Makefile.loader +++ b/Makefile.loader @@ -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 \ diff --git a/docs/bootloader.md b/docs/bootloader.md index 4aa40df5..001bf073 100644 --- a/docs/bootloader.md +++ b/docs/bootloader.md @@ -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 diff --git a/micropython/bootloader/crypto.c b/micropython/bootloader/crypto.c index 58358b95..fe8339c3 100644 --- a/micropython/bootloader/crypto.c +++ b/micropython/bootloader/crypto.c @@ -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)); } diff --git a/micropython/bootloader/main.c b/micropython/bootloader/main.c index 95398b41..e72f764f 100644 --- a/micropython/bootloader/main.c +++ b/micropython/bootloader/main.c @@ -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"); diff --git a/micropython/firmware/header.S b/micropython/firmware/header.S index b14013f7..5ea8e810 100644 --- a/micropython/firmware/header.S +++ b/micropython/firmware/header.S @@ -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: diff --git a/micropython/firmware/memory.ld b/micropython/firmware/memory.ld index c223b0c9..4f9bddae 100644 --- a/micropython/firmware/memory.ld +++ b/micropython/firmware/memory.ld @@ -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 */ diff --git a/micropython/loader/crypto.c b/micropython/loader/crypto.c new file mode 100644 index 00000000..a26a1180 --- /dev/null +++ b/micropython/loader/crypto.c @@ -0,0 +1,105 @@ +#include + +#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)); +} diff --git a/micropython/loader/crypto.h b/micropython/loader/crypto.h new file mode 100644 index 00000000..9d618cdf --- /dev/null +++ b/micropython/loader/crypto.h @@ -0,0 +1,11 @@ +#ifndef __LOADER_CRYPTO_H__ +#define __LOADER_CRYPTO_H__ + +#include +#include + +bool parse_header(const uint8_t *data, uint32_t *codelen, uint8_t *sigidx, uint8_t *sig); + +bool check_signature(const uint8_t *start); + +#endif diff --git a/micropython/loader/header.S b/micropython/loader/header.S index ecba753c..7bd93367 100644 --- a/micropython/loader/header.S +++ b/micropython/loader/header.S @@ -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: diff --git a/micropython/loader/main.c b/micropython/loader/main.c index 2bc1c5fd..479fed92 100644 --- a/micropython/loader/main.c +++ b/micropython/loader/main.c @@ -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; } diff --git a/micropython/loader/memory.ld b/micropython/loader/memory.ld index 651995f0..0651b48b 100644 --- a/micropython/loader/memory.ld +++ b/micropython/loader/memory.ld @@ -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 */ diff --git a/tools/binctl b/tools/binctl index 398f6d9d..883c297b 100755 --- a/tools/binctl +++ b/tools/binctl @@ -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('