From 5b5e4a8b3ebd41b5af1b90c55582c6ca8158cffa Mon Sep 17 00:00:00 2001 From: Ondrej Mikle Date: Sat, 30 Jul 2022 00:14:37 +0200 Subject: [PATCH 01/30] feat(legacy): better debugability for various PRODUCTION bootloader/FW settings --- docs/legacy/index.md | 7 ++++-- legacy/bootloader/.changelog.d/2423.fixed | 1 + legacy/bootloader/bootloader.c | 6 +++++ legacy/startup.S | 29 +++++++++++++++++++---- 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 legacy/bootloader/.changelog.d/2423.fixed diff --git a/docs/legacy/index.md b/docs/legacy/index.md index 49f9c59fc..276e43ee6 100644 --- a/docs/legacy/index.md +++ b/docs/legacy/index.md @@ -110,6 +110,9 @@ trezorctl firmware-update -f build/legacy/firmware/firmware.bin ## Combining bootloader and firmware with various `PRODUCTION` settings, signed/unsigned +This is an issue before firmware 1.11.2, historical versions need to be built according +to this table. + Not all combinations of bootloader and firmware will work. This depends on 3 variables: PRODUCTION of bootloader, PRODUCTION of firmware, whether firmware is signed @@ -118,9 +121,9 @@ This table shows the result for bootloader 1.8.0+ and 1.9.1+: | Bootloader PRODUCTION | Firmware PRODUCTION | Is firmware officially signed? | Result | | ------------------------- | ----------------------- | ------------------------------ | ------------------------------------------------------------------------------------------ | | 1 | 1 | yes | works, official configuration | -| 1 | 1 | no | hardfault in header.S when setting VTOR and stack | +| 1 | 1 | no | hardfault in startup.S when setting VTOR and stack | | 0 | 1 | no | works, but don't forget to comment out `check_and_replace_bootloader`, otherwise it'll get overwritten | -| 0 | 0 | no | hard fault because header.S doesn't set VTOR and stack right | +| 0 | 0 | no | hard fault because startup.S doesn't set VTOR and stack right | | 1 | 0 | no | works | The other three possibilities with signed firmware and `PRODUCTION!=0` for bootloader/firmware don't exist. diff --git a/legacy/bootloader/.changelog.d/2423.fixed b/legacy/bootloader/.changelog.d/2423.fixed new file mode 100644 index 000000000..7e3c90472 --- /dev/null +++ b/legacy/bootloader/.changelog.d/2423.fixed @@ -0,0 +1 @@ +Better way to debug T1 combinations of debug/production combinations of bootloader and firmware diff --git a/legacy/bootloader/bootloader.c b/legacy/bootloader/bootloader.c index 31c046f45..0dfa42c72 100644 --- a/legacy/bootloader/bootloader.c +++ b/legacy/bootloader/bootloader.c @@ -161,6 +161,12 @@ int main(void) { if (SIG_OK != signed_firmware) { show_unofficial_warning(fingerprint); } +#if !PRODUCTION + // try to avoid bricking board SWD debug by accident + else { + show_halt("Official firmware", "Won't flash on debug device"); + } +#endif if (SIG_OK != check_firmware_hashes(hdr)) { show_halt("Broken firmware", "detected."); diff --git a/legacy/startup.S b/legacy/startup.S index 85478934a..ee847cf00 100644 --- a/legacy/startup.S +++ b/legacy/startup.S @@ -20,9 +20,30 @@ memset_reg: .type reset_handler, STT_FUNC reset_handler: -#if PRODUCTION -// we need to perform this in case an old bootloader (<1.8.0) -// is starting the new firmware, these will be set incorrectly +// We need to perform VTOR+stack setup case an old bootloader (<1.8.0) +// is starting the new firmware, these will be set incorrectly. + +// To make development easier, set only if we are in privileged +// mode. This resolves annoying combinations of PRODUCTION +// settings for bootloader and FW. +// Normally only signed firmware will let bootloader start FW +// in privileged mode (PRODUCTION=1 variants with signed everything). +// But with devel bootloader we let FW start in privileged mode +// and let's do the check if we can set VTOR without fault + +// Since this startup code is shared with bootloader and FW, +// a) in case of bootloader MCU starts in privileged mode, +// so the jump to "setup_as_unprivileged" never happens. +// VTOR and stack are set from MCU startup +// b) in case of FW it will attempt to set VTOR and stack +// which will work for both signed bootloader+FW, but +// also for other variants with debug bootloader and +// unsigned FW or official bootloader and usigned FW + mrs r3, control + and r3, r3, #1 + cmp r3, #1 + beq .setup_as_unprivileged + ldr r0, =0xE000ED08 // r0 = VTOR address ldr r1, =0x08010400 // r1 = FLASH_APP_START str r1, [r0] // assign @@ -30,8 +51,8 @@ reset_handler: msr msp, r0 // set stack pointer dsb isb -#endif + .setup_as_unprivileged: ldr r0, =_stay_in_bootloader_flag_addr // r0 - address of storage for "stay in bootloader" flag ldr r11, [r0] // r11 - keep in register and hope it gets to main From 71e0b8c1c3ec5a0d312e6a65bae056fdb43c9abd Mon Sep 17 00:00:00 2001 From: Ondrej Mikle Date: Thu, 4 Aug 2022 01:02:14 +0200 Subject: [PATCH 02/30] feat(legacy): make debugging less painful, remove PRODUCTION macro matrix, split reset_handlers --- legacy/Makefile | 1 - legacy/bootloader/Makefile | 2 +- legacy/bootloader/startup.S | 79 ++++++++++++++++++++++++++++++++ legacy/firmware/Makefile | 1 + legacy/{ => firmware}/startup.S | 23 ++++------ legacy/intermediate_fw/Makefile | 1 + legacy/intermediate_fw/startup.S | 1 + 7 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 legacy/bootloader/startup.S rename legacy/{ => firmware}/startup.S (75%) create mode 120000 legacy/intermediate_fw/startup.S diff --git a/legacy/Makefile b/legacy/Makefile index 2f6f9eb50..5fb84a7ff 100644 --- a/legacy/Makefile +++ b/legacy/Makefile @@ -1,6 +1,5 @@ ifneq ($(EMULATOR),1) OBJS += setup.o -OBJS += startup.o OBJS += timer.o endif diff --git a/legacy/bootloader/Makefile b/legacy/bootloader/Makefile index dd8b0b866..7df46de99 100644 --- a/legacy/bootloader/Makefile +++ b/legacy/bootloader/Makefile @@ -1,5 +1,6 @@ NAME = bootloader-unaligned +OBJS += startup.o OBJS += bootloader.o OBJS += usb.o @@ -18,7 +19,6 @@ OBJS += ../oled.small.o OBJS += ../random_delays.small.o OBJS += ../rng.small.o OBJS += ../setup.small.o -OBJS += ../startup.o OBJS += ../supervise.small.o OBJS += ../timer.small.o OBJS += ../usb21_standard.small.o diff --git a/legacy/bootloader/startup.S b/legacy/bootloader/startup.S new file mode 100644 index 000000000..4f88c532b --- /dev/null +++ b/legacy/bootloader/startup.S @@ -0,0 +1,79 @@ + .syntax unified + + .text + + .global memset_reg + .type memset_reg, STT_FUNC +memset_reg: + // call with the following (note that the arguments are not validated prior to use): + // r0 - address of first word to write (inclusive) + // r1 - address of first word following the address in r0 to NOT write (exclusive) + // r2 - word value to be written + // both addresses in r0 and r1 needs to be divisible by 4! + .L_loop_begin: + str r2, [r0], 4 // store the word in r2 to the address in r0, post-indexed + cmp r0, r1 + bne .L_loop_begin + bx lr + + .global reset_handler + .type reset_handler, STT_FUNC +reset_handler: + + ldr r0, =_stay_in_bootloader_flag_addr // r0 - address of storage for "stay in bootloader" flag + ldr r11, [r0] // r11 - keep in register and hope it gets to main + + ldr r0, =_ram_start // r0 - point to beginning of SRAM + ldr r1, =_ram_end // r1 - point to byte after the end of SRAM + ldr r2, =0 // r2 - the byte-sized value to be written + bl memset_reg + + // copy .data section from flash to SRAM + ldr r0, =_data // dst addr + ldr r1, =_data_loadaddr // src addr + ldr r2, =_data_size // length in bytes + bl memcpy + + // enter the application code + bl main + + // shutdown if the application code returns + b shutdown + + .global shutdown + .type shutdown, STT_FUNC +shutdown: + cpsid f + ldr r0, =0 + mov r1, r0 + mov r2, r0 + mov r3, r0 + mov r4, r0 + mov r5, r0 + mov r6, r0 + mov r7, r0 + mov r8, r0 + mov r9, r0 + mov r10, r0 + mov r11, r0 + mov r12, r0 + ldr lr, =0xffffffff + ldr r0, =_ram_start + ldr r1, =_ram_end + // set to value in r2 + bl memset_reg + b . // loop forever + + .ltorg // dump literal pool (for the ldr ...,=... commands above) + + .global sv_call_handler + .type sv_call_handler, STT_FUNC + +sv_call_handler: + tst lr, #4 + ite eq + mrseq r0, msp + mrsne r0, psp + b svc_handler_main + + .end diff --git a/legacy/firmware/Makefile b/legacy/firmware/Makefile index 75a46b6ec..d0bca5aa5 100644 --- a/legacy/firmware/Makefile +++ b/legacy/firmware/Makefile @@ -40,6 +40,7 @@ OBJS += usb.o OBJS += bl_check.o OBJS += otp.o OBJS += header.o +OBJS += startup.o endif OBJS += messages.o diff --git a/legacy/startup.S b/legacy/firmware/startup.S similarity index 75% rename from legacy/startup.S rename to legacy/firmware/startup.S index ee847cf00..81264a6ac 100644 --- a/legacy/startup.S +++ b/legacy/firmware/startup.S @@ -20,7 +20,7 @@ memset_reg: .type reset_handler, STT_FUNC reset_handler: -// We need to perform VTOR+stack setup case an old bootloader (<1.8.0) +// We need to perform VTOR setup case an old bootloader (<1.8.0) // is starting the new firmware, these will be set incorrectly. // To make development easier, set only if we are in privileged @@ -31,14 +31,14 @@ reset_handler: // But with devel bootloader we let FW start in privileged mode // and let's do the check if we can set VTOR without fault -// Since this startup code is shared with bootloader and FW, -// a) in case of bootloader MCU starts in privileged mode, -// so the jump to "setup_as_unprivileged" never happens. -// VTOR and stack are set from MCU startup -// b) in case of FW it will attempt to set VTOR and stack -// which will work for both signed bootloader+FW, but -// also for other variants with debug bootloader and -// unsigned FW or official bootloader and usigned FW + // These two instructions are just for debug testing how unprivileged + // FW is handled + //mov r0, 1 + //msr control, r0 // set unprivileged + + ldr sp, =_stack // setup stack + + // are we privileged? if so, fix VTOR, otherwise skip mrs r3, control and r3, r3, #1 cmp r3, #1 @@ -47,15 +47,10 @@ reset_handler: ldr r0, =0xE000ED08 // r0 = VTOR address ldr r1, =0x08010400 // r1 = FLASH_APP_START str r1, [r0] // assign - ldr r0, =_stack // r0 = stack pointer - msr msp, r0 // set stack pointer dsb isb .setup_as_unprivileged: - ldr r0, =_stay_in_bootloader_flag_addr // r0 - address of storage for "stay in bootloader" flag - ldr r11, [r0] // r11 - keep in register and hope it gets to main - ldr r0, =_ram_start // r0 - point to beginning of SRAM ldr r1, =_ram_end // r1 - point to byte after the end of SRAM ldr r2, =0 // r2 - the byte-sized value to be written diff --git a/legacy/intermediate_fw/Makefile b/legacy/intermediate_fw/Makefile index 2e94591a0..35255a2f9 100644 --- a/legacy/intermediate_fw/Makefile +++ b/legacy/intermediate_fw/Makefile @@ -5,6 +5,7 @@ NAME = trezor OBJS += trezor.o OBJS += header.o OBJS += bl_check.o +OBJS += startup.o OBJS += ../vendor/trezor-crypto/memzero.o OBJS += ../vendor/trezor-crypto/sha2.o diff --git a/legacy/intermediate_fw/startup.S b/legacy/intermediate_fw/startup.S new file mode 120000 index 000000000..379dbac45 --- /dev/null +++ b/legacy/intermediate_fw/startup.S @@ -0,0 +1 @@ +../firmware/startup.S \ No newline at end of file From 8ea3ca4c5e1be424d012c1bf5f5b935a46dee1e2 Mon Sep 17 00:00:00 2001 From: Ondrej Mikle Date: Thu, 15 Sep 2022 15:51:14 +0200 Subject: [PATCH 03/30] fix(legacy): better stack setup for debug devices --- legacy/util.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/legacy/util.h b/legacy/util.h index 9ecb05936..d0b31c7ef 100644 --- a/legacy/util.h +++ b/legacy/util.h @@ -80,7 +80,11 @@ jump_to_firmware(const vector_table_t *ivt, int trust) { } else { // untrusted firmware timer_init(); mpu_config_firmware(); // * configure MPU for the firmware - __asm__ volatile("msr msp, %0" ::"r"(_stack)); + + // Setup stack in unprivileged mode (MSR works only for privileged) + // This syntax will use _stack as immediate value to put into SP + // instead of dereferencing it + __asm__ volatile("mov sp, %[input]" ::[input] "r"(&_stack)); } // Jump to address From 0ee61fee5cdc4babee331794fc3114351825787d Mon Sep 17 00:00:00 2001 From: Ondrej Mikle Date: Thu, 15 Sep 2022 17:48:39 +0200 Subject: [PATCH 04/30] fix(demo): fix build [no changelog] --- legacy/demo/Makefile | 1 + legacy/demo/startup.S | 1 + 2 files changed, 2 insertions(+) create mode 120000 legacy/demo/startup.S diff --git a/legacy/demo/Makefile b/legacy/demo/Makefile index eba48a4f2..6fd3f0985 100644 --- a/legacy/demo/Makefile +++ b/legacy/demo/Makefile @@ -3,6 +3,7 @@ APPVER = 1.0.0 NAME = demo OBJS += demo.o +OBJS += startup.o OBJS += ../vendor/trezor-crypto/bignum.o OBJS += ../vendor/trezor-crypto/bip32.o diff --git a/legacy/demo/startup.S b/legacy/demo/startup.S new file mode 120000 index 000000000..efb899b49 --- /dev/null +++ b/legacy/demo/startup.S @@ -0,0 +1 @@ +../bootloader/startup.S \ No newline at end of file From 2b48ced3308c09f80af7e1113544f45f68ba8b04 Mon Sep 17 00:00:00 2001 From: Ondrej Mikle Date: Mon, 19 Sep 2022 13:06:59 +0200 Subject: [PATCH 05/30] chore(legacy): changelogs --- legacy/firmware/.changelog.d/163.fixed | 1 + legacy/intermediate_fw/.changelog.d/163.fixed | 1 + 2 files changed, 2 insertions(+) create mode 100644 legacy/firmware/.changelog.d/163.fixed create mode 100644 legacy/intermediate_fw/.changelog.d/163.fixed diff --git a/legacy/firmware/.changelog.d/163.fixed b/legacy/firmware/.changelog.d/163.fixed new file mode 100644 index 000000000..c21b51ff2 --- /dev/null +++ b/legacy/firmware/.changelog.d/163.fixed @@ -0,0 +1 @@ +Bootloader VTOR and FW handover fix diff --git a/legacy/intermediate_fw/.changelog.d/163.fixed b/legacy/intermediate_fw/.changelog.d/163.fixed new file mode 100644 index 000000000..c21b51ff2 --- /dev/null +++ b/legacy/intermediate_fw/.changelog.d/163.fixed @@ -0,0 +1 @@ +Bootloader VTOR and FW handover fix From 29e064f07ba5144c89cf34806f5e442be18a0200 Mon Sep 17 00:00:00 2001 From: matejcik Date: Fri, 21 Oct 2022 14:33:51 +0200 Subject: [PATCH 06/30] chore(legacy/bootloader): drop unused VERSION_x_CHAR [no changelog] --- legacy/bootloader/version.h | 4 ---- tools/bump-version.py | 3 --- 2 files changed, 7 deletions(-) diff --git a/legacy/bootloader/version.h b/legacy/bootloader/version.h index c0c3d1f88..364d46dd4 100644 --- a/legacy/bootloader/version.h +++ b/legacy/bootloader/version.h @@ -1,7 +1,3 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 11 #define VERSION_PATCH 0 - -#define VERSION_MAJOR_CHAR "\x01" -#define VERSION_MINOR_CHAR "\x0B" -#define VERSION_PATCH_CHAR "\x00" diff --git a/tools/bump-version.py b/tools/bump-version.py index cfd974a74..230ff02b9 100755 --- a/tools/bump-version.py +++ b/tools/bump-version.py @@ -88,9 +88,6 @@ def cli(project, version): VERSION_MAJOR=major, VERSION_MINOR=minor, VERSION_PATCH=patch, - VERSION_MAJOR_CHAR=hex_lit(major), - VERSION_MINOR_CHAR=hex_lit(minor), - VERSION_PATCH_CHAR=hex_lit(patch), ) else: raise click.ClickException(f"Unknown project {project}.") From e6491bb3c7db7756fd37a19c2c0e178c8720b1a7 Mon Sep 17 00:00:00 2001 From: matejcik Date: Fri, 21 Oct 2022 15:27:52 +0200 Subject: [PATCH 07/30] build(legacy): add bootloader updater script --- legacy/script/update_bootloader.py | 138 +++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100755 legacy/script/update_bootloader.py diff --git a/legacy/script/update_bootloader.py b/legacy/script/update_bootloader.py new file mode 100755 index 000000000..7e32f58ea --- /dev/null +++ b/legacy/script/update_bootloader.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import typing as t +from hashlib import sha256 +from pathlib import Path + +import click + +LEGACY_ROOT = Path(__file__).parent.parent.resolve() + +BOOTLOADER_BUILT = LEGACY_ROOT / "bootloader" / "bootloader.bin" +BOOTLOADER_IMAGE = LEGACY_ROOT / "firmware" / "bootloader.dat" + +BOOTLOADER_VERSION = LEGACY_ROOT / "bootloader" / "version.h" +FIRMWARE_VERSION = LEGACY_ROOT / "firmware" / "version.h" + +BL_CHECK_C = LEGACY_ROOT / "firmware" / "bl_check.c" +BL_CHECK_TXT = LEGACY_ROOT / "firmware" / "bl_check.txt" + +BL_CHECK_PATTERN = """\ + if (0 == + memcmp(hash, + {line1} + {line2}, + 32)) + return 1; // {comment} +""" + +BL_CHECK_AUTO_BEGIN = " // BEGIN AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)\n" +BL_CHECK_AUTO_END = " // END AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)\n" + + +def cstrify(data: bytes) -> str: + """Convert bytes to C string literal. + + >>> cstrify(b"foo") + '"\\x66\\x6f\\x6f"' + """ + return '"' + "".join(rf"\x{b:02x}" for b in data) + '"' + + +def load_version(filename: Path) -> str: + """Load version from version.h""" + vdict = {} + with open(filename) as f: + for line in f: + if line.startswith("#define VERSION_"): + _define, symbol, value = line.split() + _, name = symbol.lower().split("_", maxsplit=1) + vdict[name] = int(value) + + return "{major}.{minor}.{patch}".format(**vdict) + + +def load_hash_entries() -> dict[bytes, str]: + """Load hash entries from bl_check.txt""" + return { + bytes.fromhex(digest): comment + for digest, comment in ( + line.split(" ", maxsplit=1) + for line in BL_CHECK_TXT.read_text().splitlines() + ) + } + + +def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: + """Regenerate bl_check.c with given hash entries.""" + bl_check_new = [] + with open(BL_CHECK_C) as f: + # read up to AUTO-BEGIN + for line in f: + bl_check_new.append(line) + if line == BL_CHECK_AUTO_BEGIN: + break + + # generate new sections + for digest, comment in hash_entries: + bl_check_new.append( + BL_CHECK_PATTERN.format( + line1=cstrify(digest[:16]), + line2=cstrify(digest[16:]), + comment=comment, + ) + ) + + # consume up to AUTO-END + for line in f: + if line == BL_CHECK_AUTO_END: + bl_check_new.append(line) + break + + # add rest of the file contents + bl_check_new.extend(f) + + BL_CHECK_C.write_text("".join(bl_check_new)) + + +@click.command() +@click.option("-c", "--comment", help="Comment for the hash entry.") +def main(comment: str | None) -> None: + """Insert a new bootloader image. + + Takes bootloader/boootloader.dat, copies over firmware/bootloader.dat, and adds + an entry to firmware/bl_check.txt and bl_check.c + """ + bl_bytes = BOOTLOADER_BUILT.read_bytes() + digest = sha256(sha256(bl_bytes).digest()).digest() + click.echo("Bootloader digest: " + digest.hex()) + + entries = load_hash_entries() + if digest in entries: + click.echo("Bootloader already in bl_check.txt: " + entries[digest]) + + else: + if comment is None: + bl_version = load_version(BOOTLOADER_VERSION) + fw_version = load_version(FIRMWARE_VERSION) + comment = f"{bl_version} shipped with fw {fw_version}" + + # insert new bootloader + with open(BL_CHECK_TXT, "a") as f: + f.write(f"{digest.hex()} {comment}\n") + + entries[digest] = comment + click.echo("Inserted new entry: " + comment) + + # rewrite bl_check.c + regenerate_bl_check(entries.items()) + click.echo("Regenerated bl_check.c") + + # overwrite bootloader.dat + BOOTLOADER_IMAGE.write_bytes(bl_bytes) + click.echo("Installed bootloader.dat into firmware") + + +if __name__ == "__main__": + main() From 9d64039245d4f8c83e289f45519a39158e186fa7 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Wed, 23 Nov 2022 17:17:37 +0100 Subject: [PATCH 08/30] chore(legacy): bump versions to 1.12.x --- legacy/bootloader/version.h | 2 +- legacy/firmware/version.h | 6 +++--- legacy/intermediate_fw/version.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/legacy/bootloader/version.h b/legacy/bootloader/version.h index 364d46dd4..ebb892251 100644 --- a/legacy/bootloader/version.h +++ b/legacy/bootloader/version.h @@ -1,3 +1,3 @@ #define VERSION_MAJOR 1 -#define VERSION_MINOR 11 +#define VERSION_MINOR 12 #define VERSION_PATCH 0 diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h index fe5d48b3b..234cdbd7a 100644 --- a/legacy/firmware/version.h +++ b/legacy/firmware/version.h @@ -1,7 +1,7 @@ #define VERSION_MAJOR 1 -#define VERSION_MINOR 11 -#define VERSION_PATCH 3 +#define VERSION_MINOR 12 +#define VERSION_PATCH 0 #define FIX_VERSION_MAJOR 1 -#define FIX_VERSION_MINOR 11 +#define FIX_VERSION_MINOR 12 #define FIX_VERSION_PATCH 0 diff --git a/legacy/intermediate_fw/version.h b/legacy/intermediate_fw/version.h index 7c7293249..262093176 100644 --- a/legacy/intermediate_fw/version.h +++ b/legacy/intermediate_fw/version.h @@ -1,8 +1,8 @@ // Matches the bootloader version included in this firmware #define VERSION_MAJOR 1 -#define VERSION_MINOR 10 +#define VERSION_MINOR 12 #define VERSION_PATCH 0 #define FIX_VERSION_MAJOR 1 -#define FIX_VERSION_MINOR 10 +#define FIX_VERSION_MINOR 12 #define FIX_VERSION_PATCH 0 From 3cd47f85afef632454b0a87a31b23e34ed1fe77f Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Wed, 23 Nov 2022 16:02:05 +0100 Subject: [PATCH 09/30] feat(legacy): qa build for upgrade testing --- core/Makefile | 4 +- core/SConscript.firmware | 17 +- .../firmware/bootloaders/bootloader_1.bin | 1 + .../bootloader_T.bin} | Bin legacy/Makefile.include | 12 + legacy/bootloader/bootloader.c | 10 +- legacy/bootloader/firmware_sign_dev.py | 333 ++++++++++++++++++ legacy/firmware/Makefile | 10 +- legacy/firmware/bl_check.c | 79 ++++- legacy/firmware/bl_check_qa.txt | 1 + legacy/firmware/bl_data.py | 36 +- legacy/firmware/bootloader_qa.dat | Bin 0 -> 52962 bytes legacy/fw_signatures.c | 28 +- legacy/intermediate_fw/Makefile | 11 +- legacy/intermediate_fw/bootloader_qa.dat | 1 + legacy/memory.c | 10 +- legacy/script/update_bootloader.py | 54 ++- legacy/setup.c | 2 +- 18 files changed, 565 insertions(+), 44 deletions(-) create mode 120000 core/embed/firmware/bootloaders/bootloader_1.bin rename core/embed/firmware/{bootloader.bin => bootloaders/bootloader_T.bin} (100%) create mode 100644 legacy/bootloader/firmware_sign_dev.py create mode 100644 legacy/firmware/bl_check_qa.txt create mode 100644 legacy/firmware/bootloader_qa.dat create mode 120000 legacy/intermediate_fw/bootloader_qa.dat diff --git a/core/Makefile b/core/Makefile index 78ed915e3..9861c6c8e 100644 --- a/core/Makefile +++ b/core/Makefile @@ -21,9 +21,11 @@ CROSS_PORT_OPTS ?= PRODUCTION ?= 0 PYOPT ?= 1 BITCOIN_ONLY ?= 0 +BOOTLOADER_QA ?= 0 TREZOR_MODEL ?= T TREZOR_MEMPERF ?= 0 ADDRESS_SANITIZER ?= 0 +UI2 ?= 0 # OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h OPENOCD_INTERFACE ?= stlink @@ -164,7 +166,7 @@ build_reflash: ## build reflash firmware + reflash image dd if=build/bootloader/bootloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=49152 build_firmware: templates build_cross ## build firmware with frozen modules - $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" $(FIRMWARE_BUILD_DIR)/firmware.bin + $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" BOOTLOADER_QA="$(BOOTLOADER_QA)" $(FIRMWARE_BUILD_DIR)/firmware.bin build_unix: templates ## build unix port $(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) TREZOR_MODEL="$(TREZOR_MODEL)" PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 911f74365..023a53965 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -5,10 +5,15 @@ import os import tools BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0') +PRODUCTION = ARGUMENTS.get('PRODUCTION', '0') +BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1' EVERYTHING = BITCOIN_ONLY != '1' TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') DMA2D = TREZOR_MODEL in ('T', ) +if PRODUCTION != '1' and BOOTLOADER_QA: + raise ValueError('Firmware variant for bootloader upgrade testing must be done with PRODUCTION=1') + FEATURE_FLAGS = { "RDI": True, "SECP256K1_ZKP": True, # required for trezor.crypto.curve.bip340 (BIP340/Taproot) @@ -431,7 +436,7 @@ tools.add_font('MONO', FONT_MONO, CPPDEFINES_MOD, SOURCE_MOD) SOURCE_QSTR = SOURCE_MOD + SOURCE_MICROPYTHON + SOURCE_MICROPYTHON_SPEED -env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s -DPYOPT=%s -DBITCOIN_ONLY=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0'), PYOPT, BITCOIN_ONLY)) +env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s -DPYOPT=%s -DBITCOIN_ONLY=%s' % (ARGUMENTS.get('CFLAGS', ''), PRODUCTION, PYOPT, BITCOIN_ONLY)) tools.configure_board(TREZOR_MODEL, env, CPPDEFINES_MOD, SOURCE_TREZORHAL) @@ -769,12 +774,18 @@ obj_program.extend( ' --rename-section .data=.vendorheader,alloc,load,readonly,contents' ' $SOURCE $TARGET', )) + +BOOTLOADER_SUFFIX = TREZOR_MODEL + ('_QA' if BOOTLOADER_QA else '') + obj_program.extend( env.Command( - target='embed/firmware/bootloader.o', - source='embed/firmware/bootloader.bin', + target='embed/firmware/bootloaders/bootloader.o', + source=f'embed/firmware/bootloaders/bootloader_{BOOTLOADER_SUFFIX}.bin', action='$OBJCOPY -I binary -O elf32-littlearm -B arm' ' --rename-section .data=.bootloader' + f' --redefine-sym _binary_embed_firmware_bootloaders_bootloader_{BOOTLOADER_SUFFIX}_bin_start=_binary_embed_firmware_bootloader_bin_start' + f' --redefine-sym _binary_embed_firmware_bootloaders_bootloader_{BOOTLOADER_SUFFIX}_bin_end=_binary_embed_firmware_bootloader_bin_end' + f' --redefine-sym _binary_embed_firmware_bootloaders_bootloader_{BOOTLOADER_SUFFIX}_bin_size=_binary_embed_firmware_bootloader_bin_size' ' $SOURCE $TARGET', )) env.Depends(obj_program, qstr_generated) diff --git a/core/embed/firmware/bootloaders/bootloader_1.bin b/core/embed/firmware/bootloaders/bootloader_1.bin new file mode 120000 index 000000000..6a019c96b --- /dev/null +++ b/core/embed/firmware/bootloaders/bootloader_1.bin @@ -0,0 +1 @@ +../../../../legacy/firmware/bootloader.dat \ No newline at end of file diff --git a/core/embed/firmware/bootloader.bin b/core/embed/firmware/bootloaders/bootloader_T.bin similarity index 100% rename from core/embed/firmware/bootloader.bin rename to core/embed/firmware/bootloaders/bootloader_T.bin diff --git a/legacy/Makefile.include b/legacy/Makefile.include index f7cbd6ec2..20fd6ae9c 100644 --- a/legacy/Makefile.include +++ b/legacy/Makefile.include @@ -156,6 +156,18 @@ CPUFLAGS += -DPRODUCTION=1 $(info PRODUCTION=1) endif +BOOTLOADER_QA ?= 0 +ifeq ($(BOOTLOADER_QA), 0) +CFLAGS += -DBOOTLOADER_QA=0 +CPUFLAGS += -DBOOTLOADER_QA=0 +$(info BOOTLOADER_QA=0) +else +CFLAGS += -DBOOTLOADER_QA=1 +CPUFLAGS += -DBOOTLOADER_QA=1 +$(info BOOTLOADER_QA=1) +endif + + ifeq ($(DEBUG_RNG), 1) CFLAGS += -DDEBUG_RNG=1 else diff --git a/legacy/bootloader/bootloader.c b/legacy/bootloader/bootloader.c index 0dfa42c72..66c1d46bb 100644 --- a/legacy/bootloader/bootloader.c +++ b/legacy/bootloader/bootloader.c @@ -64,6 +64,7 @@ void show_unplug(const char *line1, const char *line2) { "You may now", "unplug your Trezor.", NULL); } +#if !BOOTLOADER_QA static void show_unofficial_warning(const uint8_t *hash) { // On production bootloader, show warning and wait for user // to accept or reject it @@ -93,6 +94,7 @@ static void show_unofficial_warning(const uint8_t *hash) { delay(100000000); #endif } +#endif static void __attribute__((noreturn)) load_app(int signed_firmware) { // zero out SRAM @@ -159,12 +161,16 @@ int main(void) { uint8_t fingerprint[32] = {0}; int signed_firmware = signatures_match(hdr, fingerprint); if (SIG_OK != signed_firmware) { +#if BOOTLOADER_QA + show_halt("Unsigned firmware", "Won't run on QA device"); +#else show_unofficial_warning(fingerprint); +#endif } -#if !PRODUCTION +#if !PRODUCTION && !BOOTLOADER_QA && !DEBUG_T1_SIGNATURES // try to avoid bricking board SWD debug by accident else { - show_halt("Official firmware", "Won't flash on debug device"); + show_halt("Official firmware", "Won't run on debug device"); } #endif diff --git a/legacy/bootloader/firmware_sign_dev.py b/legacy/bootloader/firmware_sign_dev.py new file mode 100644 index 000000000..af66ce1ee --- /dev/null +++ b/legacy/bootloader/firmware_sign_dev.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 +import argparse +import hashlib +import struct + +import ecdsa +from ecdsa import BadSignatureError + +SLOTS = 3 + +pubkeys_dev = { + 1: "042c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991ae31a9c671a36543f46cea8fce6984608aa316aa0472a7eed08847440218cb2f", + 2: "04edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f2211452c88a66eb8ac3c19a1cc3a3fc6d72506f6fce2025f738d8b55f29f22125eb0a4", + 3: "04665f660a5052be7a95546a02179058d93d3e08a779734914594346075bb0afd45113948d72cf3dc8f2b70ee02dc1695d051bb0c6da2a914a69045e3277682d3b", +} + +privkeys_dev = { + 1: "0x4444444444444444444444444444444444444444444444444444444444444444", + 2: "0x4545454545454545454545454545454545454545454545454545454545454545", + 3: "0xbfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8", +} + +FWHEADER_SIZE = 1024 +SIGNATURES_START = 6 * 4 + 8 + 512 +INDEXES_START = SIGNATURES_START + 3 * 64 + +INDEXES_START_OLD = len("TRZR") + struct.calcsize(" size: + raise ValueError("Chunk too big already") + if len(data) == size: + return data + return data + b"\xFF" * (size - len(data)) + + +# see memory.h for details + + +def prepare_hashes(data): + # process chunks + start = 0 + end = (64 - 1) * 1024 + hashes = [] + for i in range(16): + sector = data[start:end] + if len(sector) > 0: + chunk = pad_to_size(sector, end - start) + hashes.append(hashlib.sha256(chunk).digest()) + else: + hashes.append(b"\x00" * 32) + start = end + end += 64 * 1024 + return hashes + + +def check_hashes(data): + expected_hashes = data[0x20 : 0x20 + 16 * 32] + hashes = b"" + for h in prepare_hashes(data[FWHEADER_SIZE:]): + hashes += h + + if expected_hashes == hashes: + print("HASHES OK") + else: + print("HASHES NOT OK") + + +def update_hashes_in_header(data): + # Store hashes in the firmware header + data = bytearray(data) + o = 0 + for h in prepare_hashes(data[FWHEADER_SIZE:]): + data[0x20 + o : 0x20 + o + 32] = h + o += 32 + return bytes(data) + + +def get_header(data, zero_signatures=False): + if not zero_signatures: + return data[:FWHEADER_SIZE] + else: + data = bytearray(data[:FWHEADER_SIZE]) + data[SIGNATURES_START : SIGNATURES_START + 3 * 64 + 3] = b"\x00" * (3 * 64 + 3) + return bytes(data) + + +def check_size(data): + size = struct.unpack(" 32768: @@ -18,18 +21,19 @@ with open("bl_data.h", "wt") as f: f.write(f"static const uint8_t bl_hash[32] = {{{bl_hash}}};\n") f.write(f"static const uint8_t bl_data[32768] = {{{bl_data}}};\n") -# make sure the last item listed in known_bootloader function -# is our bootloader -with open("bl_check.c", "rt") as f: - hashes = [] - for l in f.readlines(): - if not len(l) >= 78 or not l.startswith(' "\\x'): - continue - l = l[14:78] - h = "" - for i in range(0, len(l), 4): - h += l[i + 2 : i + 4] - hashes.append(h) - check = hashes[-2] + hashes[-1] - if check != bh.hex(): - raise Exception("bootloader hash not listed in bl_check.c") +if fn != "bootloader_qa.dat": + # make sure the last item listed in known_bootloader function + # is our bootloader + with open("bl_check.c", "rt") as f: + hashes = [] + for l in f.readlines(): + if not len(l) >= 78 or not l.startswith(' "\\x'): + continue + l = l[14:78] + h = "" + for i in range(0, len(l), 4): + h += l[i + 2 : i + 4] + hashes.append(h) + check = hashes[-2] + hashes[-1] + if check != bh.hex(): + raise Exception("bootloader hash not listed in bl_check.c") diff --git a/legacy/firmware/bootloader_qa.dat b/legacy/firmware/bootloader_qa.dat new file mode 100644 index 0000000000000000000000000000000000000000..2ddcc7534cb059685967182069d6d501da6dcc5a GIT binary patch literal 52962 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 BzNG*F literal 0 HcmV?d00001 diff --git a/legacy/fw_signatures.c b/legacy/fw_signatures.c index 4317f8b9b..c887b1e40 100644 --- a/legacy/fw_signatures.c +++ b/legacy/fw_signatures.c @@ -64,6 +64,30 @@ static const uint8_t * const pubkey_v3[PUBKEYS] = { // the "new", or second signing scheme keys +#if BOOTLOADER_QA +/* + Previously used for QA bootloader upgrade tests + + Debug private keys for v2 (previously called "new") scheme + corresponding to pubkeys below as python hexstring array: + + + ['4444444444444444444444444444444444444444444444444444444444444444', + '4545454545454545454545454545454545454545454545454545454545454545', + 'bfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8', + '5518381d95e93e8eb68a294354989906e3828f36b4556a2ad85d8333294eb1b7', + '1d1d34168760dec092c9ff89377d8659076d2dfd95e0281719c15f90d067e211'] + */ + +static const uint8_t * const pubkey_v2[PUBKEYS] = { + (const uint8_t *)"\x03\x2c\x0b\x7c\xf9\x53\x24\xa0\x7d\x05\x39\x8b\x24\x01\x74\xdc\x0c\x2b\xe4\x44\xd9\x6b\x15\x9a\xa6\xc7\xf7\xb1\xe6\x68\x68\x09\x91", + (const uint8_t *)"\x02\xed\xab\xbd\x16\xb4\x1c\x83\x71\xb9\x2e\xf2\xf0\x4c\x11\x85\xb4\xf0\x3b\x6d\xcd\x52\xba\x9b\x78\xd9\xd7\xc8\x9c\x8f\x22\x11\x45", + (const uint8_t *)"\x03\x66\x5f\x66\x0a\x50\x52\xbe\x7a\x95\x54\x6a\x02\x17\x90\x58\xd9\x3d\x3e\x08\xa7\x79\x73\x49\x14\x59\x43\x46\x07\x5b\xb0\xaf\xd4", + (const uint8_t *)"\x03\x66\x63\x5d\x99\x94\x17\xb6\x55\x66\x86\x6c\x65\x63\x0d\x97\x7a\x7a\xe7\x23\xfe\x5f\x6c\x4c\xd1\x7f\xa0\x0f\x08\x8b\xa1\x84\xc1", + (const uint8_t *)"\x03\xf3\x6c\x7d\x0f\xb6\x15\xad\xa4\x3d\x71\x88\x58\x0f\x15\xeb\xda\x22\xd6\xf6\xb9\xb1\xa9\x2b\xff\x16\xc6\x93\x77\x99\xdc\xbc\x66" + }; +#else + /* Debug private keys for v2 (previously called "new") scheme corresponding to pubkeys below as python hexstring array: @@ -81,8 +105,10 @@ static const uint8_t * const pubkey_v2[PUBKEYS] = { (const uint8_t *)"\x03\x66\x63\x5d\x99\x94\x17\xb6\x55\x66\x86\x6c\x65\x63\x0d\x97\x7a\x7a\xe7\x23\xfe\x5f\x6c\x4c\xd1\x7f\xa0\x0f\x08\x8b\xa1\x84\xc1", (const uint8_t *)"\x03\xf3\x6c\x7d\x0f\xb6\x15\xad\xa4\x3d\x71\x88\x58\x0f\x15\xeb\xda\x22\xd6\xf6\xb9\xb1\xa9\x2b\xff\x16\xc6\x93\x77\x99\xdc\xbc\x66" }; -#else +#endif +#else +#error NOT IMPLEMENTED // These public keys are production keys // - used in production devices diff --git a/legacy/intermediate_fw/Makefile b/legacy/intermediate_fw/Makefile index 35255a2f9..166ddbfb9 100644 --- a/legacy/intermediate_fw/Makefile +++ b/legacy/intermediate_fw/Makefile @@ -44,10 +44,15 @@ endif @printf " MAKO $@\n" $(Q)$(PYTHON) ../vendor/trezor-common/tools/cointool.py render $(MAKO_RENDER_FLAG) $@.mako +ifeq ($(BOOTLOADER_QA), 0) bl_data.h: bl_data.py bootloader.dat - @printf " PYTHON bl_data.py\n" - $(Q)$(PYTHON) bl_data.py - + @printf " PYTHON bl_data.py bootloader.dat\n" + $(Q)$(PYTHON) bl_data.py bootloader.dat +else +bl_data.h: bl_data.py bootloader_qa.dat + @printf " PYTHON bl_data.py bootloader_qa.dat\n" + $(Q)$(PYTHON) bl_data.py bootloader_qa.dat +endif clean:: rm -f bl_data.h find -maxdepth 1 -name "*.mako" | sed 's/.mako$$//' | xargs rm -f diff --git a/legacy/intermediate_fw/bootloader_qa.dat b/legacy/intermediate_fw/bootloader_qa.dat new file mode 120000 index 000000000..b50597d6f --- /dev/null +++ b/legacy/intermediate_fw/bootloader_qa.dat @@ -0,0 +1 @@ +../firmware/bootloader_qa.dat \ No newline at end of file diff --git a/legacy/memory.c b/legacy/memory.c index 39cebb3f9..9f66b94ca 100644 --- a/legacy/memory.c +++ b/legacy/memory.c @@ -30,6 +30,9 @@ void memory_protect(void) { #if PRODUCTION +#if BOOTLOADER_QA +#error BOOTLOADER_QA must be built with PRODUCTION=0 +#endif // Reference STM32F205 Flash programming manual revision 5 // http://www.st.com/resource/en/programming_manual/cd00233952.pdf Section 2.6 // Option bytes @@ -73,11 +76,16 @@ void memory_protect(void) { // example in STM32F427, where the protection bits are read correctly // from OPTION_BYTES and not form FLASH_OPCTR register. // -// Read protection is unaffected and always stays locked to the desired value. +// Read protection is set to level 2. void memory_write_unlock(void) { +#if PRODUCTION +#if BOOTLOADER_QA +#error BOOTLOADER_QA must be built with PRODUCTION=0 +#endif flash_unlock_option_bytes(); flash_program_option_bytes(0x0FFFCCEC); flash_lock_option_bytes(); +#endif } int memory_bootloader_hash(uint8_t *hash) { diff --git a/legacy/script/update_bootloader.py b/legacy/script/update_bootloader.py index 7e32f58ea..14b281430 100755 --- a/legacy/script/update_bootloader.py +++ b/legacy/script/update_bootloader.py @@ -11,12 +11,14 @@ LEGACY_ROOT = Path(__file__).parent.parent.resolve() BOOTLOADER_BUILT = LEGACY_ROOT / "bootloader" / "bootloader.bin" BOOTLOADER_IMAGE = LEGACY_ROOT / "firmware" / "bootloader.dat" +BOOTLOADER_QA_IMAGE = LEGACY_ROOT / "firmware" / "bootloader_qa.dat" BOOTLOADER_VERSION = LEGACY_ROOT / "bootloader" / "version.h" FIRMWARE_VERSION = LEGACY_ROOT / "firmware" / "version.h" BL_CHECK_C = LEGACY_ROOT / "firmware" / "bl_check.c" BL_CHECK_TXT = LEGACY_ROOT / "firmware" / "bl_check.txt" +BL_CHECK_QA_TXT = LEGACY_ROOT / "firmware" / "bl_check_qa.txt" BL_CHECK_PATTERN = """\ if (0 == @@ -30,6 +32,13 @@ BL_CHECK_PATTERN = """\ BL_CHECK_AUTO_BEGIN = " // BEGIN AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)\n" BL_CHECK_AUTO_END = " // END AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)\n" +BL_CHECK_AUTO_QA_BEGIN = ( + " // BEGIN AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt)\n" +) +BL_CHECK_AUTO_QA_END = ( + " // END AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt)\n" +) + def cstrify(data: bytes) -> str: """Convert bytes to C string literal. @@ -53,25 +62,28 @@ def load_version(filename: Path) -> str: return "{major}.{minor}.{patch}".format(**vdict) -def load_hash_entries() -> dict[bytes, str]: +def load_hash_entries(txt_file) -> dict[bytes, str]: """Load hash entries from bl_check.txt""" return { bytes.fromhex(digest): comment for digest, comment in ( - line.split(" ", maxsplit=1) - for line in BL_CHECK_TXT.read_text().splitlines() + line.split(" ", maxsplit=1) for line in txt_file.read_text().splitlines() ) } -def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: +def regenerate_bl_check( + hash_entries: t.Iterable[tuple[bytes, str]], + begin, + end, +) -> None: """Regenerate bl_check.c with given hash entries.""" bl_check_new = [] with open(BL_CHECK_C) as f: # read up to AUTO-BEGIN for line in f: bl_check_new.append(line) - if line == BL_CHECK_AUTO_BEGIN: + if line == begin: break # generate new sections @@ -86,7 +98,7 @@ def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: # consume up to AUTO-END for line in f: - if line == BL_CHECK_AUTO_END: + if line == end: bl_check_new.append(line) break @@ -98,7 +110,14 @@ def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: @click.command() @click.option("-c", "--comment", help="Comment for the hash entry.") -def main(comment: str | None) -> None: +@click.option( + "--qa", + is_flag=True, + show_default=True, + default=False, + help="Install as QA bootloader.", +) +def main(comment: str | None, qa: bool) -> None: """Insert a new bootloader image. Takes bootloader/boootloader.dat, copies over firmware/bootloader.dat, and adds @@ -108,7 +127,12 @@ def main(comment: str | None) -> None: digest = sha256(sha256(bl_bytes).digest()).digest() click.echo("Bootloader digest: " + digest.hex()) - entries = load_hash_entries() + txt_file = BL_CHECK_QA_TXT if qa else BL_CHECK_TXT + begin = BL_CHECK_AUTO_QA_BEGIN if qa else BL_CHECK_AUTO_BEGIN + end = BL_CHECK_AUTO_QA_END if qa else BL_CHECK_AUTO_END + image = BOOTLOADER_QA_IMAGE if qa else BOOTLOADER_IMAGE + + entries = load_hash_entries(txt_file) if digest in entries: click.echo("Bootloader already in bl_check.txt: " + entries[digest]) @@ -119,19 +143,23 @@ def main(comment: str | None) -> None: comment = f"{bl_version} shipped with fw {fw_version}" # insert new bootloader - with open(BL_CHECK_TXT, "a") as f: + with open(txt_file, "a") as f: f.write(f"{digest.hex()} {comment}\n") entries[digest] = comment click.echo("Inserted new entry: " + comment) - + # rewrite bl_check.c - regenerate_bl_check(entries.items()) + regenerate_bl_check(entries.items(), begin, end) click.echo("Regenerated bl_check.c") # overwrite bootloader.dat - BOOTLOADER_IMAGE.write_bytes(bl_bytes) - click.echo("Installed bootloader.dat into firmware") + image.write_bytes(bl_bytes) + + if qa: + click.echo("Installed bootloader_qa.dat into firmware") + else: + click.echo("Installed bootloader.dat into firmware") if __name__ == "__main__": diff --git a/legacy/setup.c b/legacy/setup.c index 21ad3c37d..1537c3fb1 100644 --- a/legacy/setup.c +++ b/legacy/setup.c @@ -221,7 +221,7 @@ void mpu_config_bootloader(void) { // Never use in bootloader! Disables access to PPB (including MPU, NVIC, SCB) void mpu_config_firmware(void) { -#if PRODUCTION +#if PRODUCTION || BOOTLOADER_QA // Disable MPU MPU_CTRL = 0; From 335ac476a241852697578355f87fa0d3227a066b Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Mon, 19 Dec 2022 21:20:08 +0100 Subject: [PATCH 10/30] feat(legacy): filled production keys, 2 out of 3 signatures --- legacy/bootloader/usb.c | 6 +- legacy/debug_signing/README.md | 8 +- legacy/debug_signing/fill_t1_fw_signatures.py | 4 +- .../firmware_hash_sign_trezor.py | 4 +- .../sign_firmware_v2_signature.py | 12 +-- legacy/firmware/fsm_msg_common.h | 4 +- legacy/fw_signatures.c | 85 ++++++++----------- legacy/fw_signatures.h | 3 +- 8 files changed, 56 insertions(+), 70 deletions(-) diff --git a/legacy/bootloader/usb.c b/legacy/bootloader/usb.c index e3290bd38..7412f34ed 100644 --- a/legacy/bootloader/usb.c +++ b/legacy/bootloader/usb.c @@ -186,7 +186,7 @@ static int should_keep_storage(int old_was_signed, const image_header *new_hdr = (const image_header *)FW_HEADER; // new header must be signed by v3 signmessage/verifymessage scheme - if (SIG_OK != signatures_ok(new_hdr, NULL, true)) return SIG_FAIL; + if (SIG_OK != signatures_ok(new_hdr, NULL, sectrue)) return SIG_FAIL; // if the new header hashes don't match flash contents, erase storage if (SIG_OK != check_firmware_hashes(new_hdr)) return SIG_FAIL; @@ -401,7 +401,7 @@ static void rx_callback(usbd_device *dev, uint8_t ep) { flash_state = STATE_CHECK; const image_header *hdr = (const image_header *)FW_HEADER; // allow only v3 signmessage/verifymessage signature for new FW - if (SIG_OK != signatures_ok(hdr, NULL, true)) { + if (SIG_OK != signatures_ok(hdr, NULL, sectrue)) { send_msg_buttonrequest_firmwarecheck(dev); return; } @@ -417,7 +417,7 @@ static void rx_callback(usbd_device *dev, uint8_t ep) { bool hash_check_ok; // show fingerprint of unsigned firmware // allow only v3 signmessage/verifymessage signatures - if (SIG_OK != signatures_ok(hdr, NULL, true)) { + if (SIG_OK != signatures_ok(hdr, NULL, sectrue)) { if (msg_id != 0x001B) { // ButtonAck message (id 27) return; } diff --git a/legacy/debug_signing/README.md b/legacy/debug_signing/README.md index 7b4044e70..d8ad3f837 100644 --- a/legacy/debug_signing/README.md +++ b/legacy/debug_signing/README.md @@ -8,12 +8,12 @@ 1. **FW header hash is different from whole FW hash in the one output by cibuild** 1. Run the emulator or device (make sure not to confuse which are you using for signing) 1. Run `firmware_hash_sign_trezor.py ../firmware/trezor.bin ../firmware/trezor.bin.signed` -1. Accept 3 signature requests on signing device -1. This will show you a list of 3 signatures for 3 keys +1. Accept 2 signature requests on signing device +1. This will show you a list of 2 signatures for 3 keys 1. It will output `../firmware.trezor.bin.signed` -By default the scripts uses the `[1, 2, 3]` sigindices, you can modify `sig_indices` -inside to have different order or different keys (1 <= index <= 5 ) +By default the scripts uses the `[1, 2]` sigindices, you can modify `sig_indices` +inside to have different order or different keys (1 <= index <= 3 ) Update FW on T1 either via `trezorctl device firmware-update` or `make flash_firmware_jlink`. diff --git a/legacy/debug_signing/fill_t1_fw_signatures.py b/legacy/debug_signing/fill_t1_fw_signatures.py index 97f1ceec7..e3629d723 100755 --- a/legacy/debug_signing/fill_t1_fw_signatures.py +++ b/legacy/debug_signing/fill_t1_fw_signatures.py @@ -45,9 +45,9 @@ class Signatures: Patch signatures from signature_pairs. Requires filling signature_pairs beforehand. """ - assert len(self.signature_pairs) == 3 + assert len(self.signature_pairs) <= 3 - for i in range(3): + for i in range(len(self.signature_pairs)): sigindex_ofs = self.sigindex_offsets[i] sig_ofs = self.sig_offsets[i] (sigindex, sig) = self.signature_pairs[i] diff --git a/legacy/debug_signing/firmware_hash_sign_trezor.py b/legacy/debug_signing/firmware_hash_sign_trezor.py index b662565a1..c11893da4 100755 --- a/legacy/debug_signing/firmware_hash_sign_trezor.py +++ b/legacy/debug_signing/firmware_hash_sign_trezor.py @@ -16,8 +16,8 @@ except IndexError: out_fw_fname = in_fw_fname + ".signed" # These 3 keys will be used in this order to sign the FW -# each index can be >= 1 and <= 5 -sig_indices = [1, 2, 3] +# each index can be >= 1 and <= 3 +sig_indices = [1, 2] print(f"Input trezor.bin file: {in_fw_fname}") print(f"Output signed trezor.bin file: {out_fw_fname}") diff --git a/legacy/debug_signing/sign_firmware_v2_signature.py b/legacy/debug_signing/sign_firmware_v2_signature.py index 686ebfe96..6abecfc2b 100755 --- a/legacy/debug_signing/sign_firmware_v2_signature.py +++ b/legacy/debug_signing/sign_firmware_v2_signature.py @@ -10,9 +10,9 @@ import ecdsa from fill_t1_fw_signatures import Signatures secret_keys_hex = [ - "ba7994923c91771ad77c483f7d2b41f5506b82aa900e6f12edeae96c5c9f8f66", - "81a825d359da7ec9534e6cf7dd190bdbad62e134265764a5ec3e63317b060a51", - "37107a021e50ca3571102691606083f6a8d9cd600e35cd2c8e8f7b87796a045b", + "4444444444444444444444444444444444444444444444444444444444444444", + "4545454545454545454545454545454545454545454545454545454545454545", + "bfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8", "5518381d95e93e8eb68a294354989906e3828f36b4556a2ad85d8333294eb1b7", "1d1d34168760dec092c9ff89377d8659076d2dfd95e0281719c15f90d067e211", ] @@ -46,9 +46,9 @@ print(f"Output signed trezor.bin file: {out_fw_fname}") # Should be these public keys assert public_keys_hex == [ - "0391cdaf3cac08c2712ee1e88cd6d346eb2c798fdaf95c0eb6efeea0d7014dac87", - "02186ff4e2b08bc5ae0e21a508f1ced48ff451eab7d794deb0b7cbe8efd729aba7", - "0324009f0b398ca1c335fb17a1021c3f7fb0831ddb28348a4f058b149ea4c589a0", + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", + "03665f660a5052be7a95546a02179058d93d3e08a779734914594346075bb0afd4", "0366635d999417b65566866c65630d977a7ae723fe5f6c4cd17fa00f088ba184c1", "03f36c7d0fb615ada43d7188580f15ebda22d6f6b9b1a92bff16c6937799dcbc66", ] diff --git a/legacy/firmware/fsm_msg_common.h b/legacy/firmware/fsm_msg_common.h index 16c712927..a0903bb21 100644 --- a/legacy/firmware/fsm_msg_common.h +++ b/legacy/firmware/fsm_msg_common.h @@ -24,8 +24,8 @@ bool get_features(Features *resp) { #else const image_header *hdr = (const image_header *)FLASH_PTR(FLASH_FWHEADER_START); - // only v3 signatures count as signed - if (SIG_OK == signatures_ok(hdr, NULL, true)) { + // allow both v2 and v3 signatures + if (SIG_OK == signatures_match(hdr, NULL)) { strlcpy(resp->fw_vendor, "SatoshiLabs", sizeof(resp->fw_vendor)); } else { strlcpy(resp->fw_vendor, "UNSAFE, DO NOT USE!", sizeof(resp->fw_vendor)); diff --git a/legacy/fw_signatures.c b/legacy/fw_signatures.c index c887b1e40..24b0046c2 100644 --- a/legacy/fw_signatures.c +++ b/legacy/fw_signatures.c @@ -23,6 +23,7 @@ #include "fw_signatures.h" #include "memory.h" #include "memzero.h" +#include "secbool.h" #include "secp256k1.h" #include "sha2.h" @@ -40,9 +41,10 @@ const uint32_t FIRMWARE_MAGIC_NEW = 0x465a5254; // TRZF * * Latest scheme v3 ref: https://github.com/trezor/trezor-firmware/issues/2513 */ -#define PUBKEYS 5 +#define PUBKEYS_V3 3 +#define PUBKEYS_V2 5 -#if DEBUG_T1_SIGNATURES +#if DEBUG_T1_SIGNATURES || BOOTLOADER_QA // Make build explode if combining debug sigs with production #if PRODUCTION @@ -54,24 +56,18 @@ const uint32_t FIRMWARE_MAGIC_NEW = 0x465a5254; // TRZF // "table table table table table table table table table table table advance" // the "SignMessage"-style public keys, third signing scheme // See legacy/debug_signing/README.md -static const uint8_t * const pubkey_v3[PUBKEYS] = { +static const uint8_t * const pubkey_v3[PUBKEYS_V3] = { (const uint8_t *)"\x03\x73\x08\xe1\x40\x77\x16\x1c\x36\x5d\xea\x0f\x5c\x80\xaa\x6c\x5d\xba\x34\x71\x9e\x82\x5b\xd2\x3a\xe5\xf7\xe7\xd2\x98\x8a\xdb\x0f", (const uint8_t *)"\x03\x9c\x1b\x24\x60\xe3\x43\x71\x2e\x98\x2e\x07\x32\xe7\xed\x17\xf6\x0d\xe4\xc9\x33\x06\x5b\x71\x70\xd9\x9c\x6e\x7f\xe7\xcc\x7f\x4b", (const uint8_t *)"\x03\x15\x2b\x37\xfd\xf1\x26\x11\x12\x74\xc8\x94\xc3\x48\xdc\xc9\x75\xb5\x7c\x11\x5e\xe2\x4c\xeb\x19\xb5\x19\x0a\xc7\xf7\xb6\x51\x73", - (const uint8_t *)"\x02\x83\x91\x8a\xbf\x1b\x6e\x1d\x2a\x1d\x08\xea\x29\xc9\xd2\xae\x2e\x97\xe3\xc0\x1f\x4e\xa8\x59\x2b\x08\x8d\x28\x03\x5b\x44\x4e\xd0", - (const uint8_t *)"\x03\xc6\x83\x63\x85\x07\x8a\x18\xe8\x1d\x74\x77\x68\x1e\x0d\x30\x86\x66\xf3\x99\x59\x4b\xe9\xe8\xab\xcb\x45\x58\xa6\xe2\x47\x32\xfc" }; // the "new", or second signing scheme keys -#if BOOTLOADER_QA /* - Previously used for QA bootloader upgrade tests - Debug private keys for v2 (previously called "new") scheme corresponding to pubkeys below as python hexstring array: - ['4444444444444444444444444444444444444444444444444444444444444444', '4545454545454545454545454545454545454545454545454545454545454545', 'bfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8', @@ -79,50 +75,27 @@ static const uint8_t * const pubkey_v3[PUBKEYS] = { '1d1d34168760dec092c9ff89377d8659076d2dfd95e0281719c15f90d067e211'] */ -static const uint8_t * const pubkey_v2[PUBKEYS] = { +static const uint8_t * const pubkey_v2[PUBKEYS_V2] = { (const uint8_t *)"\x03\x2c\x0b\x7c\xf9\x53\x24\xa0\x7d\x05\x39\x8b\x24\x01\x74\xdc\x0c\x2b\xe4\x44\xd9\x6b\x15\x9a\xa6\xc7\xf7\xb1\xe6\x68\x68\x09\x91", (const uint8_t *)"\x02\xed\xab\xbd\x16\xb4\x1c\x83\x71\xb9\x2e\xf2\xf0\x4c\x11\x85\xb4\xf0\x3b\x6d\xcd\x52\xba\x9b\x78\xd9\xd7\xc8\x9c\x8f\x22\x11\x45", (const uint8_t *)"\x03\x66\x5f\x66\x0a\x50\x52\xbe\x7a\x95\x54\x6a\x02\x17\x90\x58\xd9\x3d\x3e\x08\xa7\x79\x73\x49\x14\x59\x43\x46\x07\x5b\xb0\xaf\xd4", (const uint8_t *)"\x03\x66\x63\x5d\x99\x94\x17\xb6\x55\x66\x86\x6c\x65\x63\x0d\x97\x7a\x7a\xe7\x23\xfe\x5f\x6c\x4c\xd1\x7f\xa0\x0f\x08\x8b\xa1\x84\xc1", (const uint8_t *)"\x03\xf3\x6c\x7d\x0f\xb6\x15\xad\xa4\x3d\x71\x88\x58\x0f\x15\xeb\xda\x22\xd6\xf6\xb9\xb1\xa9\x2b\xff\x16\xc6\x93\x77\x99\xdc\xbc\x66" }; -#else - -/* - Debug private keys for v2 (previously called "new") scheme - corresponding to pubkeys below as python hexstring array: - ['ba7994923c91771ad77c483f7d2b41f5506b82aa900e6f12edeae96c5c9f8f66', - '81a825d359da7ec9534e6cf7dd190bdbad62e134265764a5ec3e63317b060a51', - '37107a021e50ca3571102691606083f6a8d9cd600e35cd2c8e8f7b87796a045b', - '5518381d95e93e8eb68a294354989906e3828f36b4556a2ad85d8333294eb1b7', - '1d1d34168760dec092c9ff89377d8659076d2dfd95e0281719c15f90d067e211'] - */ -static const uint8_t * const pubkey_v2[PUBKEYS] = { - (const uint8_t *)"\x03\x91\xcd\xaf\x3c\xac\x08\xc2\x71\x2e\xe1\xe8\x8c\xd6\xd3\x46\xeb\x2c\x79\x8f\xda\xf9\x5c\x0e\xb6\xef\xee\xa0\xd7\x01\x4d\xac\x87", - (const uint8_t *)"\x02\x18\x6f\xf4\xe2\xb0\x8b\xc5\xae\x0e\x21\xa5\x08\xf1\xce\xd4\x8f\xf4\x51\xea\xb7\xd7\x94\xde\xb0\xb7\xcb\xe8\xef\xd7\x29\xab\xa7", - (const uint8_t *)"\x03\x24\x00\x9f\x0b\x39\x8c\xa1\xc3\x35\xfb\x17\xa1\x02\x1c\x3f\x7f\xb0\x83\x1d\xdb\x28\x34\x8a\x4f\x05\x8b\x14\x9e\xa4\xc5\x89\xa0", - (const uint8_t *)"\x03\x66\x63\x5d\x99\x94\x17\xb6\x55\x66\x86\x6c\x65\x63\x0d\x97\x7a\x7a\xe7\x23\xfe\x5f\x6c\x4c\xd1\x7f\xa0\x0f\x08\x8b\xa1\x84\xc1", - (const uint8_t *)"\x03\xf3\x6c\x7d\x0f\xb6\x15\xad\xa4\x3d\x71\x88\x58\x0f\x15\xeb\xda\x22\xd6\xf6\xb9\xb1\xa9\x2b\xff\x16\xc6\x93\x77\x99\xdc\xbc\x66" -}; -#endif - -#else -#error NOT IMPLEMENTED +#else // DEBUG_T1_SIGNATURES is now 0 // These public keys are production keys // - used in production devices // the "SignMessage"-style public keys, third signing scheme -static const uint8_t * const pubkey_v3[PUBKEYS] = { - (const uint8_t *)"\x02\xd5\x71\xb7\xf1\x48\xc5\xe4\x23\x2c\x38\x14\xf7\x77\xd8\xfa\xea\xf1\xa8\x42\x16\xc7\x8d\x56\x9b\x71\x04\x1f\xfc\x76\x8a\x5b\x2d", - (const uint8_t *)"\x03\x63\x27\x9c\x0c\x08\x66\xe5\x0c\x05\xc7\x99\xd3\x2b\xd6\xba\xb0\x18\x8b\x6d\xe0\x65\x36\xd1\x10\x9d\x2e\xd9\xce\x76\xcb\x33\x5c", - (const uint8_t *)"\x02\x43\xae\xdb\xb6\xf7\xe7\x1c\x56\x3f\x8e\xd2\xef\x64\xec\x99\x81\x48\x25\x19\xe7\xef\x4f\x4a\xa9\x8b\x27\x85\x4e\x8c\x49\x12\x6d", - (const uint8_t *)"\x02\x87\x7c\x39\xfd\x7c\x62\x23\x7e\x03\x82\x35\xe9\xc0\x75\xda\xb2\x61\x63\x0f\x78\xee\xb8\xed\xb9\x24\x87\x15\x9f\xff\xed\xfd\xf6", - (const uint8_t *)"\x03\x73\x84\xc5\x1a\xe8\x1a\xdd\x0a\x52\x3a\xdb\xb1\x86\xc9\x1b\x90\x6f\xfb\x64\xc2\xc7\x65\x80\x2b\xf2\x6d\xbd\x13\xbd\xf1\x2c\x31" +static const uint8_t * const pubkey_v3[PUBKEYS_V3] = { + (const uint8_t *)"\x03\x23\x00\xc1\xbb\x45\x39\xfc\xbf\xca\x25\x90\xbd\xa3\xdd\x20\x93\x82\x6f\x4a\xe4\x37\xbd\xde\xcc\x1a\x2e\x72\x52\x07\x64\xff\x7a", + (const uint8_t *)"\x02\x33\xba\xea\xeb\xc9\x4a\x2a\x3e\x8b\x11\xf3\x9a\x71\x33\xdb\xf4\x27\xbe\x29\x2f\xcb\xce\xb8\x87\xd7\x1e\xf5\x1e\x85\x39\x5a\x19", + (const uint8_t *)"\x03\x57\x09\x1f\xa2\x54\xb5\x52\x33\xd0\xbb\x4c\x48\xe1\x06\xc9\x1b\x92\xfd\x07\x88\xeb\xed\x9d\x3a\x91\x67\x19\xf4\x4c\x76\xc0\x15" }; // the "new", or second signing scheme keys -static const uint8_t * const pubkey_v2[PUBKEYS] = { +static const uint8_t * const pubkey_v2[PUBKEYS_V2] = { (const uint8_t *)"\x02\xd5\x71\xb7\xf1\x48\xc5\xe4\x23\x2c\x38\x14\xf7\x77\xd8\xfa\xea\xf1\xa8\x42\x16\xc7\x8d\x56\x9b\x71\x04\x1f\xfc\x76\x8a\x5b\x2d", (const uint8_t *)"\x03\x63\x27\x9c\x0c\x08\x66\xe5\x0c\x05\xc7\x99\xd3\x2b\xd6\xba\xb0\x18\x8b\x6d\xe0\x65\x36\xd1\x10\x9d\x2e\xd9\xce\x76\xcb\x33\x5c", (const uint8_t *)"\x02\x43\xae\xdb\xb6\xf7\xe7\x1c\x56\x3f\x8e\xd2\xef\x64\xec\x99\x81\x48\x25\x19\xe7\xef\x4f\x4a\xa9\x8b\x27\x85\x4e\x8c\x49\x12\x6d", @@ -131,8 +104,6 @@ static const uint8_t * const pubkey_v2[PUBKEYS] = { }; #endif -#define SIGNATURES 3 - #define FLASH_META_START 0x08008000 #define FLASH_META_CODELEN (FLASH_META_START + 0x0004) #define FLASH_META_SIGINDEX1 (FLASH_META_START + 0x0008) @@ -197,28 +168,35 @@ bool firmware_present_new(void) { } int signatures_ok(const image_header *hdr, uint8_t store_fingerprint[32], - bool use_verifymessage) { + secbool use_verifymessage) { uint8_t hash[32] = {0}; // which set of public keys depend on scheme const uint8_t *const *pubkey_ptr = NULL; - if (use_verifymessage) { + uint8_t pubkeys = 0; + if (use_verifymessage == sectrue) { pubkey_ptr = pubkey_v3; compute_firmware_fingerprint_for_verifymessage(hdr, hash); + pubkeys = PUBKEYS_V3; } else { pubkey_ptr = pubkey_v2; compute_firmware_fingerprint(hdr, hash); + pubkeys = PUBKEYS_V2; } if (store_fingerprint) { memcpy(store_fingerprint, hash, 32); } - if (hdr->sigindex1 < 1 || hdr->sigindex1 > PUBKEYS) + if (hdr->sigindex1 < 1 || hdr->sigindex1 > pubkeys) return SIG_FAIL; // invalid index - if (hdr->sigindex2 < 1 || hdr->sigindex2 > PUBKEYS) + if (hdr->sigindex2 < 1 || hdr->sigindex2 > pubkeys) return SIG_FAIL; // invalid index - if (hdr->sigindex3 < 1 || hdr->sigindex3 > PUBKEYS) + if (use_verifymessage != sectrue && + (hdr->sigindex3 < 1 || hdr->sigindex3 > pubkeys)) { return SIG_FAIL; // invalid index + } else if (hdr->sigindex3 != 0) { + return SIG_FAIL; + } if (hdr->sigindex1 == hdr->sigindex2) return SIG_FAIL; // duplicate use if (hdr->sigindex1 == hdr->sigindex3) return SIG_FAIL; // duplicate use @@ -232,9 +210,16 @@ int signatures_ok(const image_header *hdr, uint8_t store_fingerprint[32], hdr->sig2, hash)) { // failure return SIG_FAIL; } - if (0 != ecdsa_verify_digest(&secp256k1, pubkey_ptr[hdr->sigindex3 - 1], - hdr->sig3, hash)) { // failure + if (use_verifymessage != sectrue && + (0 != ecdsa_verify_digest(&secp256k1, pubkey_ptr[hdr->sigindex3 - 1], + hdr->sig3, hash))) { // failure return SIG_FAIL; + } else { + for (unsigned int i = 0; i < sizeof(hdr->sig3); i++) { + if (hdr->sig3[i] != 0) { + return SIG_FAIL; + } + } } return SIG_OK; @@ -247,8 +232,8 @@ int signatures_match(const image_header *hdr, uint8_t store_fingerprint[32]) { // timing side channels. // Return only the hash for the v2 computation so that it is // the same shown in previous bootloader. - result ^= signatures_ok(hdr, store_fingerprint, false); - result ^= signatures_ok(hdr, NULL, true); + result ^= signatures_ok(hdr, store_fingerprint, secfalse); + result ^= signatures_ok(hdr, NULL, sectrue); if (result != SIG_OK) { return SIG_FAIL; } diff --git a/legacy/fw_signatures.h b/legacy/fw_signatures.h index ab2a450bf..ffef2192f 100644 --- a/legacy/fw_signatures.h +++ b/legacy/fw_signatures.h @@ -22,6 +22,7 @@ #include #include +#include "secbool.h" extern const uint32_t FIRMWARE_MAGIC_NEW; // TRZF @@ -105,7 +106,7 @@ void compute_firmware_fingerprint_for_verifymessage(const image_header *hdr, * @return SIG_OK or SIG_FAIL */ int signatures_ok(const image_header *hdr, uint8_t store_fingerprint[32], - bool use_verifymessage); + secbool use_verifymessage); /** * Check if either v2 or v3 signature of header is valid. From 6d2b73ea868143cc9c4d2e98014c68e60aa90140 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Tue, 20 Dec 2022 10:14:50 +0100 Subject: [PATCH 11/30] feat(legacy): add model info to image header --- legacy/Makefile.include | 7 +++++++ legacy/firmware/header.S | 5 ++++- legacy/fw_signatures.h | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/legacy/Makefile.include b/legacy/Makefile.include index 20fd6ae9c..261316957 100644 --- a/legacy/Makefile.include +++ b/legacy/Makefile.include @@ -95,6 +95,13 @@ LDFLAGS += -L$(TOP_DIR) \ ZKP_PATH = $(TOP_DIR)vendor/secp256k1-zkp +# hw_model = T1B1 = 0x31423154 +HW_MODEL = 0x31423154 +CPUFLAGS += -DHW_MODEL=$(HW_MODEL) +CPUFLAGS += -DHW_REVISION=0 +CFLAGS += -DHW_MODEL=$(HW_MODEL) +CFLAGS += -DHW_REVISION=0 + ifeq ($(EMULATOR),1) CFLAGS += -DEMULATOR=1 diff --git a/legacy/firmware/header.S b/legacy/firmware/header.S index 46e4aa375..87a7dba30 100644 --- a/legacy/firmware/header.S +++ b/legacy/firmware/header.S @@ -20,7 +20,10 @@ g_header: .byte FIX_VERSION_MINOR // fix_vminor .byte FIX_VERSION_PATCH // fix_vpatch .byte 0 // fix_vbuild - . = . + 8 // reserved + .word HW_MODEL // type of the designated hardware + .byte HW_REVISION // revision of the designated hardware + .byte 0 // monotonic version placeholder - not used in legacy + . = . + 2 // reserved . = . + 512 // hash1 ... hash16 . = . + 64 // sig1 . = . + 64 // sig2 diff --git a/legacy/fw_signatures.h b/legacy/fw_signatures.h index ffef2192f..e9c76f363 100644 --- a/legacy/fw_signatures.h +++ b/legacy/fw_signatures.h @@ -44,7 +44,10 @@ typedef struct { uint32_t codelen; uint32_t version; uint32_t fix_version; - uint8_t __reserved1[8]; + uint32_t hw_model; + uint8_t hw_revision; + uint8_t monotonic; + uint8_t __reserved1[2]; uint8_t hashes[512]; uint8_t sig1[64]; uint8_t sig2[64]; From 13fd5d792dc3f60e646398a7d5f793f0e8dd49e8 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Tue, 20 Dec 2022 10:20:20 +0100 Subject: [PATCH 12/30] chore(legacy): bundle bootloader 1.12.0 into firmware --- legacy/firmware/bl_check.c | 10 ++++++++-- legacy/firmware/bl_check.txt | 1 + legacy/firmware/bl_check_qa.txt | 2 +- legacy/firmware/bootloader.dat | Bin 32768 -> 32768 bytes legacy/firmware/bootloader_qa.dat | Bin 52962 -> 32768 bytes 5 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 legacy/firmware/bl_check.txt diff --git a/legacy/firmware/bl_check.c b/legacy/firmware/bl_check.c index be064b71f..996d1fbf9 100644 --- a/legacy/firmware/bl_check.c +++ b/legacy/firmware/bl_check.c @@ -92,8 +92,8 @@ static int known_bootloader(int r, const uint8_t *hash) { // BEGIN AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt) if (0 == memcmp(hash, - "\x29\x34\xf3\xd3\xaa\x59\x2b\x34\x49\x6a\x23\xb8\x14\x74\xe2\xc2" - "\xf3\xee\x1e\x36\x66\x0b\xf0\x1e\x19\x8a\x65\x15\xc2\x32\xc1\x85", + "\x21\xb9\x49\xf4\xf5\xfd\x9f\x3a\x7d\x63\x43\xd1\x07\x6f\x96\x0f" + "\xb4\x54\x18\x19\x65\x31\xb9\xf2\x97\x4a\x68\xed\xe8\xdb\x2e\xa1", 32)) return 1; // 1.12.0 shipped with fw 1.12.0 // END AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt) @@ -214,6 +214,12 @@ static int known_bootloader(int r, const uint8_t *hash) { 32)) return 1; // 1.11.0 shipped with fw 1.11.1 // BEGIN AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt) + if (0 == + memcmp(hash, + "\xe9\xec\x8f\xa2\xfe\xfa\xd2\xb3\xb6\xb7\xc4\xab\x76\x69\x1a\x33" + "\x61\xb3\xee\xfb\x8a\x0d\xda\xa3\x4d\x7e\x45\xb6\x3b\x3d\x56\x64", + 32)) + return 1; // 1.12.0 shipped with fw 1.12.0 // END AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt) return 0; } diff --git a/legacy/firmware/bl_check.txt b/legacy/firmware/bl_check.txt new file mode 100644 index 000000000..1d9ba4afe --- /dev/null +++ b/legacy/firmware/bl_check.txt @@ -0,0 +1 @@ +e9ec8fa2fefad2b3b6b7c4ab76691a3361b3eefb8a0ddaa34d7e45b63b3d5664 1.12.0 shipped with fw 1.12.0 diff --git a/legacy/firmware/bl_check_qa.txt b/legacy/firmware/bl_check_qa.txt index 77103ab78..1850bad14 100644 --- a/legacy/firmware/bl_check_qa.txt +++ b/legacy/firmware/bl_check_qa.txt @@ -1 +1 @@ -2934f3d3aa592b34496a23b81474e2c2f3ee1e36660bf01e198a6515c232c185 1.12.0 shipped with fw 1.12.0 +21b949f4f5fd9f3a7d6343d1076f960fb45418196531b9f2974a68ede8db2ea1 1.12.0 shipped with fw 1.12.0 diff --git a/legacy/firmware/bootloader.dat b/legacy/firmware/bootloader.dat index ac9d56f2307c80722bd3ca49998efa776cac2de7..48b798065f6d36f9f15feafa17affefd7561a93e 100644 GIT binary patch delta 7507 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 delta 7484 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 diff --git a/legacy/firmware/bootloader_qa.dat b/legacy/firmware/bootloader_qa.dat index 2ddcc7534cb059685967182069d6d501da6dcc5a..f882d985ac03d333d10b3fb1ff5a7d244881e67d 100644 GIT binary patch literal 32768 zcmeFad0-Sp`afJf-E-xfWH@p`<_H)vA%p}437TPM(j*xq0dyC^-JKB7NkkF}uEAxO z0aT)RfWhw~!Ch9}UBD}e#uXJ4#r1a0aO{G}8jszP#G?lir;|yh-%s_-BmsSo@9*z- zNL5$Y@zhgKJ@wR6PgOOd#26lCh-T14G*_F6<_5TPzX&^W*>4c}K0_Lfrq5@mkL8V) z`Tw8)dzx59&eCKHdg~WWDD4(WtG;wXS%p9pQs9D=T&geAm#c*DMIw9&M_#`DC8QCC zSMV&)@k(CBt9cK~;^LUjoZIJUD)CIhO*BCW( z6he{onoZ?TQVn>C`J?k(VZ7hTvd!`S`K$u|4ECe*WVyCH#u-xUmduU% zBj&q^WuRN-G&H8UnFz$Lb_!E1vaR_Mm>jHDIXsx9~UXFY)<22(84!{RA`}k zw1te&C23oKA#%Ym%y$9rQ@CA#IrDse=@RGtz)CA)^%44<5iUrKutF+zrnnfPGRO$M z!wTUUk?bpR?z7puiEx#~@r+;=N%n&7Csp%Lk!g(ZyJ7T`;hJ(rSTsV6Rznd&H;pjD zL&F)S8NA{k^QcNF6|X8~4l%-W;+0O0-)m#L=agMpDg`!3`M}#J98eo~~g65$EupZSO(ruFF8`Aw8({%7T}L`^-FMU3XypBJqB8nl(*l5QrNbU4cI zU~>cE7)>|w4ctOB7Q|l#cMTj5w;V1L{mX&V*$98IrmaTOY&9zd_fU+JHIZhw{pDI$Q(>}C&>^wVKHb^Dv@j=GD!C&smS*Hu0!PN>X7x*D-s zxEQ!txTO3C#fg=7nQM?XMVD=SP)tV5q{=#T3Sy?}b|FTOn8|Qyl{#}AVhy^(h)qFE zdL;wsW}RW$Y{aA@CZqB}F%wTCoQXUrX5l#vZo00utrdB$(7g;lN0$`ypqQ)cLU@KD zIqpF*8+j~nGj$J&d3eq;Sj}6F2A(;r6uua=W(T}Yw+!n5^|%;Aj*AJpW%mQJ2lTHTgMRN%LO(G=F%FMw3SSKw6O~q+@L`A( zJ`K^fMx_(Q=*S35r9mIkUZ;4eA>*QDq`s-TtO^xofZ9_H6)A7)X>nC8K$fGjDw$v{kzhC zuM`%9+<13`v*(7~wC-UypOdcb;}}j@GSV^NDUR))#eXKgdw?@7MLupWC)h^*hWH;& zxcT%$obbU&ZpCc3!KCh6n3R4{Db$RVwn>3UB&F#J=M`nyrD^q2pm=0*j}%xhJ*9b8 z^JiPyX%6*rf;(gceNKxcf1fR*8@SD$T*?W53l_SW!a`RP(##P)Nd@iFNAle_idj1H z14FnzPriq6!hIoT&Od!~+{~QB+<%`o7}b3a9T8TCNcMpDbzc_bgs_9sIZMeH^5J?Q z3#u{R3BwrX;QcUUg&Xg=2!oCl&pTTyF#APjy!pJ<%zy`#!ULi;+vq)fouyG}nbNJW zD9&&et6Nd2!yRaXG&8j-}STuFPBE zC0E?-ya%(Aq5;n`ruiP{J*A`>bMl=SsLr?< z@9#%=Zg_;7UeSNorV2r;`E`XhsPg}5V5q7sDEj9C&fg@VU14Od;)Jh9;_|-ta>9xc zPPltSXhuGE5_t{pz?yS41r(maIM46ho8^Byk;IS_5ryw_EEL}0cpAD3ghmoa|p zgYHuJo=C_CpVRzOz#LM7Ze`>0ZRxLjep%V_(kQI_*A(cDb6eGU|MLFkt{BUNZmnEW zC6}CkNy%l;^JR-hOJ01H7-M*cb3d;anOjxD7Sd!@IPz7Q{G^k`&NSDx7@aQ+crLS8O>lpN+X~kTM|G>W@jM6ThZEr#%!}LLqT}H^z@TH| zS;Xyy`xD&VwJ3{c7aYcF)vFk*pAXVe`wBvz4zi%k%g%ZHRM%{NE?>yMblu+;e zTIs!CQzn}7nd!vD&1SCn+{avz{P|PP3gK`8F}9RWbra#;ptZy*ByZgd9=>~+hU2!L z5p}u??H{x^+X}k(rIDPj3yQ5$VAjZI;!WGgta;s^iE|!#bV)vcqsXOm;N_c#`H}#5 z`9hErrVi5{-ty{0&iT*?A9P;LbMPN5r6cP>Cxb9)rX%XZk#L`$9F_uC54~|wZGQWr z+SF`&+l#OrVOz7U%Uh7}W?$0kqrOe3jTmWN`KU|uzU!l${lM^sm(+(R@td5iKSeo# zPjTPq))caDJ#91b0dIQKXJYqOv!^htp`zhBVs4OiO|BZ4%)7nxL25qEhFZ9##%99k~k2z5g3h!j{nM6!$f z%bz2{;LvB{uHB%`RvNS5xy2R4NXPu6;6M6275q(&%8HTcdKW#%O_u`q4u>U$TJy7r zq_}HTQamvdl@up}R8kP*67@VrP0&I`Z7~EHP%7GzZnYmBQH}^`TPWH*rXPPzoFv zlmcH2s(D~`djTN)awHF9*pAX)J|_hhL2Hec>l~`+Ckt~+)rUFZqalMSr%a96D@LZr z*UuSvtx*d6Cpf)qI`rVr22KCzA>sLh&Ls}4Dd>D+#go48jp&$EcQHa2mKK_`<=+277pj9pGO*VXj&__CkTX#I@#xHk-WOaE`5X=#sz zj_^c~Gl%g{U!EUYhTENWxS1S=5Mtl`tZ+ebr%a@-cIM2L|!Z#EF&g-{XJ!G(>)@-Ehedq;{8 zI@V3!ySf=O?bX+ORQFi`di)b@{u1u%Mxr?Y9$EmJFiN6%3vnO8(KS#H=tVRNE7glW zNB9dkbw|voeyYa2HC~rpE^?(&ZFcG_;kk#F*LP%%mZ3VWPT&-VGNUuY8`rc(Z9JJ- zev^BT?cbd%-Auts7cr&P$292#1E&xS3WImwk;&zs%ITjV{S%tLA+{%`DH%`{-jy!K z^j{w_t#p0q`_z@OLF?3b#<0>rZ_srkw~bscHW zX9u?4hCW{%$20OO<0FxqQvtgKbYjtJybt`hA!Ic^hUa=b58+vh^{WYz#Z$!`d{F$V z;6d>}aQ`jfgl9#n>7N6HsBMD?$E0}F5MFDMTK@D}j%jaKm1E#iFMV9ZBowMg}Z%+a+f5jIH%s&Q_md{K(|T#a?Ft~|N|Npz;V zciVK{)9!fZfdNnDUfTnm!~iZB*YhlR?Pn4(^!wz#kJvS?U$&B`vX6X|K!lD-d_0}) z_?@j+IWtTOtl+{H4hGVi6JPQlF2;KBP^MQ3G)NT!8<$tHw#|YSV!S^qVIt3dbVo^w z>qb`&&-yj&58gduPr+}F5Q7<5MC}CE0t-EK+H}cKQ6VVf{_U$S`S$c4@pBW&Ip4jy z#CMw3@)gQ{kxXXvc$;ni?yeK8B{!!2Tr}z?lrm3q1DXx2@WC+2_9EBk$%&;i95jul zq#)&Wq`c+j2C^Htfq5ID9a>1vYu+bqgI;Cn5nn8mxM-*0*P3vV0 znsFu6puz5tPGpLR<@;`85F$N7%hz{M`rqF={Sx}acBf)?WBn#o^lx5hC}j?_LZxId z%?AH-!h@mGHeyH{M`a6YaD~2fvjd*$jJTF|wvCy?Onb)0ewvs&;apPb*FKDe+1VWN z6PLz8q0Im^=8&qdM8^qhu?qXAm$~x4K7&z(b(s|SAJ{)W>SpGA=8I{q53luMmx;F3 zaCNk;AGH6<^7~G$lNgr6{*cmm=_;BClR&32n;O|j`8eKrY5D&_`Iu6aCo1ROPA30S zN)LqB&_PNoT0Xad+8ZZ2m9PPY?TvB#jJ6n_G0Za(VSg~Ik(hJ%dB&a$BJ6+;kqwz# z9n_BN4Fyf;x%Z;OklvxdDTx6mEA==#xd4TM3^xAN+f0(G$9%jJA5P(Q_d&L zF^XZP#nM*9r{j&;M}#5F^(1jhH!<7znXp3=!7{8oa&wynq4mR4+w%EIut+(yt8}ys~OiD?cSdM-|yB->{G%|&(z_OP51m~W% zF+QW?^Nr!S@?KwD`PXp&#rO>K#G6OPa(&~Cj`7#7a=uF-Q#1t0c+|;7;K1@8IuDR~xT2gO~RZ;jcrRO^Tq>pEPjS7S%cEej9cN zFZ6HDJ=nQ9i!raxAX)Q`u!=TcV}|?~nhPlTv^P=m{-L}!Ghe{x%YAoUkjnirOD7Rl z4s&Qhy_E=6*e9TTt?tl_&h?i|Gy8COl!o)^otfFuv6bXxPZQG?Ls~uCqfo`1q&h(C zxUm!q%4H3AW;16PQ=cy#8qEaELt6~rTr%g}7{eGEU?=1-+c1VGYzAwFgeB7)6GSFG zpJ#<@q-SiRSL@v8-mM-!wa=Zb8a{bBrr!k>QF)ZlSk-{JWenHT#WNC5cMWW*|3Js) zzh^(tcTC5aV_=(BGwcxK*WZ+CPrCkch<^``wf#o)>;HF{JFQ7K8GBjg$1qzigGtBy zJ*`ah3@6!@$WI*Q5paEMg}`WY8*eJnd}yPbYRW*${jSEY-Phf8&vN5LXj-S7}R|l#$@BVrNA#F4YdCM_@ux#Y0#&nBlY1hAN=oVd!zNobeMb@ z9md;T$Ip*+G&4cWP*qVql@;!b@NKwHQk^u0lOdf^U&;#44Kwod z@UYI9i1&k7?|P~dk>eP0#31JJ;md1$cZe~nB6=|^Jp5WjFD44B=MK=CQ&_`wPjxcJ zspIOt3w5Le;>KZxF$K^1;cV=fGz=sVRgw` zG?fvyC?|_dqV>r55gG@m%duCc4>7P!eb-HOn<>Nj4$kp4EF0|Tp5CQ>b3!CtOiOcb zTpaig`+r23Jw(h=+De#papkFD{(K z@8xxijlG}WndN5WdBD)yv++e+(z(l9Aq>vIZl)Ay4>4Dkl!)h;Ieors_(7~2qI&X{ zoSEkX_A&UA_{aDOiys%WJyF=So-q8$=Mu)rv1GKcONLM9MHR+{cZ9j}x{?9Z@-@ar z|40CJ{UFZ^!ge}7=$M|&=albg-IA^36ZmAe@)6KVk^brXQ}~>e)B14Vm>3f)Y~ij3 z=IkRlnzUwFK%EnRC|1n3wUSxs0#moes)h--qYkhr|5^ z>QqeO)0`guzELi!ZJQ`#S|KQ6Ad6zi71vKy3R_|!mBC4g<7hMiIOV}+^W8ADa(>sX z;4M7l2q{Br8l~C%3Ot3ON{8Cf6Q+|aM*#qMxpNr60Sg>L(6&VW@R>Duh+7P;Ybf1a9%9r&o3dJMOgL9#O7Nf_aqey+4 zWEp@NGe-*iYuJ-3dM}?LUX*vK!+m~n;Z%N-Q_aWmyG9`>`I3@Y@X3p|wm;- z-UzxJ9?s@x2rC`zU&BV;is$}ey7rCoa3=K6(cMiUTO?-el{YJPF)iC| zOiSF^?Y0E3qNVys9B3Dv@e@G1Dp9dd??9gJk-*y$)4~*@R0~t2X#N~heM|?R+mYl< z8eKBTz#S5$=YGhvgK%^YnC{)HJ2)pdDru#_ zsmT8ROUR?{h#S>kVjyec=B29?r?i0grmPu`t52 z?XgJO7f8$RD0S`}-T9|`?8n6k*Ep`T_mc+pnKuzm{3!9QN55vt-Ebs}J;)08z+Tzq zVsXa6dV#_7D4q&Dj|^FVc!01%2j1_+d)rW9`EilVvkM&iCZ19tUz!DNmB!_jq6RaK z%`#INQZlGBW2eG619L|)Eci5h1;SSk9za-OG$UL%c)+AI8WCDDXvRp=89{YNCf*+# zngW0KPzLumuzCKf6&O?2r_~@1>cS zNJXr@LR32FtVrwRV7c5VvAGGSv%opJ1?GNhgT0pnpXe6Qce}uVlXMF*#=$t}l`U(H z+ov*?3#TWJOX>D1j8WL9Iu*hqDG?(}munIj#W6f-*d`ELI2^aY>^kmKV-ya-SC^-O zHx35r9^i$@eij{T!=MZ7OY(USYLko^l{I?o73*D>(^EN)p0wEk^`Ad(;6{rYoE8x>AaTDgRCyV+{p^M(3N~lpNidslRmGfztnErlsRi4XmsA51}yiFoD`LY6n>dFLr1OYyG7z@E!op)1MFS& z3{x^I^o+1rB`Y4&J7OCD(x*|CIGPyfgiEi+98d*Dw(fA`Gk?xZdH&qE!%WI&NVTGeexbMQ+T%h%R8J$P67)9 ze-^c$6yMEnIl>tJ?Ta@px36}`7wH|uq~lXJ?Y8xuIf3&ulbG9AFl`#_L-zZKne)G^ zdUB0Ys^Rk-p8q*EH>2^fz0;dj_*s*bWFgYMIu}>_&^2)Fx)e-JOgv z)pP8(Np4G<%^hzbLDsAnH49?vFPBHH!;E39?F}6n(4^k{ZB&nlH?OqY-SPAFjwze& zZ+g^bq*zu?Vw8P~WM@RxA$U%)vYY%*-R$2Q?OV@UDrY&t1wNjj6h zf^EZYh9TLlzoaEB;5&!IR^jM8uI@+}M-v5Tq5w^ZImsPcpX^?KNqNczmQhNC@zQrp z;C1E6ZbsIC)0z}HfM81V+}HG8yF&PfMCPS*LMoWd?v#47du9FQGJpjHM?{6E zqBq5$$e!HIWpui7HNP5a z63s50tu(Vijd)7rb}eX16*VqxpN4%Aa%LKTC66!ztNGqM>|C?L8Q9LAN}u8&X5{lc z)vjn(wxn%3Dr#`rMtPcTc@p?s>tPIU`xGsTed&xE>ENz>wojoyA!=HwMQ~zwi%525 z^M8V*d7poRzmZ=CIrdBtvH4H+#c)4!>~-AWc&hy+$QbN6nK{5v@1s!kj#Sj8Gwil0 zPD5iX?DS1Ea!845WrB*KbTMXEY+npZ;g&e#;6m_OO}bjARmrMwoHXn@OAK~;-p-&bi<9om*p zaOw^@^Ctp&w9Rj{eImvhfQ#*5rwO=h3$Elp5>r~C0|k+SR!~dJFHvF)e=5IU@}&rp zCa)&mt2Mt+&zUdQW8bfdvt)O2<{}SkZm6H`%J&^~Yy8qYx*y;FTpxC^GtXHsXzfQt z&alsx>a7&BE3@VPU|?$QYR?CRy|!$+w_u*2 zpwNc%hEZ9hi^w8NI>7l zX(2u0hT<0szWBx8!F>bQ>wAh1&sDBP<0Zp+FOj7}82<;evDZF>Xv!=&^PYn+-t`@m z#^Df?tLu)1d+|sT>bCxBi|*Nf9WEn@H{jZyd16`Vlr%4(}LL0h=*QXg@!SwtWsa5$)7> zm`Cf3Yn&js5({wlC6i-vle-nUQ@d|^HGT0(YN^@3Cv2(VsC2$*FicG;FtbyCS^D?T z1*tA61*$sZE=&7-H0{35@%?b>5}J(PXJ+PUXYmWB0PDPWA)&S*I;+AzNC z_&RCZe<8XqY5NBeyXKz?h^C=~wtV8amWKlG&@=krtTZpxlP5z@PK#l4^xm`W-&u5C z%*BR6m!&Bg(k|I-@r<8`X>UG&>n+T`EF-fFmx%uDKcRH|+?T4s3-jXEKdOahhZ zSr^7I$(hqs=t{>KmIpddw4ZIac)HrhkE4|CP+&k}vUMn-w8VR3%B!1_H=k|aXH$DM zPKwtf!0Qp<^#!CQtSAOpx+D7o&tW~54}A0;9PCd~Ee-qT8xygg)i~lZCE$E9_O5Vd zJ9|Q4X>F zCc8;+*?5tgb<96S!4{@irVS}Be1{PevwA$gh63BAF&%IG__~xQLV-Fd3wVXOf)(bx z9G(}(>>`XAH&co(Z_V}C=^ZPT`f^yF((}xU%u(F^29_9>iCpGBo3)oAgI=|>b;u)( z;aSmJsbkO|GsoFh)Ex>w(U~<$0hBM=juj}L>Dz4fUWOg?zA&_1P&uvIF-R0jOaw|8 zmj>*e>7ANUt0sG2lw6aCv9ry9{J3}rI62I zwLGRT6ZCSljd~sstrRj-u+FN@!Md3pknvOg2)|dbD$SSmQK17nH@oSYftA+`#K=5e zXJf8eCK|2Svvw`8=2_?1t$1T*YiC@Pt%p&&9Bt{NA>}w5dyAPD^`tH_X zz4rajMee=UlJA0XR|dR*&fCN+7c*y7qBhsw`**gY_l7(E>wU_vQ89i)LZvvp>z5+8 ziMdLf_f1fJgurp5{rI%(} zPGs-k&k}Quc$}E87je-Ajw#eyF1K5U0xNKGBE06Uhul|T-*HcClCu=DfZo*5%c~uQ zkQ6fV`8d&owY`*soeAcU6nH}_MIJ_YN4^E7A3c>6!uo$gM84Ag1l1B!ofZ9_jZtd# z^SeZIy(*Kt+K>;P`4#%L_^(0b3YiPLSfR@#LhO|$(I6u zjqD*{-Dls~dK+%yWkLsN0(HYDo+j~$A13~tIi?eQ`v_-7;WxX-YlaVCl0e^>Wx`TLxG2fW2p`x z?;q(qCYBp!MAyt%ADdVnw>|`^0J|_;O6UF7F24f2Cl#i2D*RHx12)Zx@seEwPQGP$ zm0fRTgM_RhI_tNX`@(Bz+BXS2X#bl`C+Al3Q*b&sN-t_JC#EhxsWkI0`vmNK?BNZ9 z%i0rEW31VG-(r;*UuzP3ehMcB=t&6dy+rQujLisarXinlPm;rk_H73TRrD^s zec(q}yfAJbo3^LS7R7;^AAwPUHvMWl+GC`!92qjCAGhbB(e?y7CM;&AM{PNEf@?9! zssyJk7qjZuI+T5jluHr%Ha*WV9g-HZu#)mtMeomk-S;E>ssS}BX9GAB!DL1816&TMfnJcN(pndrLkKh%JgIA9|=lgL-E;>#U!0}fMD|xDkxS>7y zgSFV%qC1*z;>4o~{6xcRhQnjvFN1X689xT%gY-mMRQ5^><6(Q5;$(J3_q>u|Uts=# zeS7IC$X%R+Q((=Ig?22t)0UmdTzeCA6zEtFuP%AFZ=v!I$3jQ0Bi5nr<&-L%#VF}z zl+(~YCFp1?x+^-w1-Y11yAE}}7*e;GpG`Y#va-EOCdsbar)o~JrX5bRvMpS% zk|38|3Y;G~cV20>^|IU-b_=vKKqD!3x^LbTWcQ^#&GsrtuGN5?D}!9ip*~g{-r;Qa zPRVNF9}D*PzsNtwzy5V1c?K@}{5LFM3(-gP77NaGj@(povJ-Ql(E@usY*{!X1Dizf zRey5gXWl91$AT9xfNHU9F!`}L{4@M(fRy-F*oQSkhD0g8HhAVI;Cwyws((u27hbx* zyJW}$E@9+;{d6eHScy4ds+eU=!pxe6CuUt?5}x39Az42Ed*!&9cGKWo%B7J#^hxsB zspzOC{tWf>%upb8kVFcM#fjXM-b!uuAhVKp6riMSo{jW;|RD-ix~_vIR3>0nep zh~=^BC}(hnA#A}-;Y&>l;USrm$IH@LC=p%P>#-j@wikgt`oZzD6P3IvxPhKU!Ye2# z&>T6>bLk$26nJ$ozm4XT0xu1Q?ZlVk*^gb8=)5K@ay$38#>>)&!|a>xPLJp@&kj=F zkMC0uh45E7oQ!es#9%gP@ps93MCs2`(J``p(1kONQs9r$&mfu7N3FMi@`M7NVk>{1 ze;GYKi$4UPhUoJ&`kn5Ox_R8G0S~&FG?bNUd(u9U`g5TlPcvf7LccafUC_f(3q~J5i zSE{9aNimE*i`>Zv!#KV-s6A}PnTurTbMN7#0fjyd_|oUZ5i>YH`LGsgucB_Zoa>vR zWVr-Ar_hQL$;cUp63GX(&q#sba8y6S{gV9TXNp{Sf15gr+q-Z-^JP$ujp(oRn*j8@ zUzBI1z`Q|g8+w5W_x9|c?Cp`j2v&yqk@;b6kn#U34G+m)2EIVx%^I=3o}qoYNv4^nge?CZ| zLFRcWkT#U~@j;ops9)sPAIbrDQR-8>>W6&B^RlFRC+t&sm4iLajDtmxSicPF4_m+y z8R+${;0#`m{>>N)KjTnmDW7pzkF@q+4)QQa{ca zR=Sc;e2b0{vn%77D327mEv^l|>Erl;uCo$(#$Otg?CsZwC~pbZ*1rS1clNuZIyzd|$@s47b# zY;OndIuhmsic6N00VLutRgRtI*~W)4Kpon|x0di0_ci=NK&KMlFg%8X4J(AJWy!u2 z^_R(^#WE%or$usHj4ag_4u)s;Rl!T;<4B+>L?O$o8(`*qHL4Q{i>OZYe(N45|xdns^QoWbYe z`Sl=u4+TupER>#mXdABq6)~PQ*jE?LSf?)J=OUkLNdH_o^zmR6vVI&c_li`~QV#!* zjJ2{`FUvM7_}ezbJe`6Orav4C=%kpZm+~q6+{4qJk9jT>(2S$bvYD~SNdeq~MJ&b9Au`XKAd5;F+QMtbQGja6*)q)v(54)Orva}Y z(DrQ5WEtXY$T1D2zQH*j8nz<-zk??5A|02LfwZBy3g!DvWuuzJ zNZ|UB@=L~{uuzGU4srZ*t+g_T(OGn6guh}!lvk3$H%d8_jt~iHWe&owuUTe?vadInFI>VWGfFC_&#sfi@{R6JmC4mjdyk7W`pG zsuZ{lZPS7qo)2NJi=%grqVS?m%y)7xQQa>6`B30~3A}PiKT#Q&9_h(Y;4bMIKI;-0 zS?H$RW#Y3ArK2Z9ff`_Dm3u9OI|p=@-Src-(%uUNCSw-JMjJLsv=?W{Gp1hdMQW=J z_gohGODp#rZOMEoU8Y(naHX7X#djH`z@QlJ!yDv1>zU*F?$^V4wC^Ho6}o;%FkhuJj&a+DY=`gI%FgRq|62upgTAE%GFOBo|EZRN3YDpv}OlDUTSuS-z*Gh&gm z7`C$y((Fp?8ZCD`*%!yA(p9Vd)e>qyrfcADyjRtp6d%Y}`6ZpjN(Q!AyV4lPvR)PY zo@mg;d&yqx)nJEli8IaJJUaL7#@sh$xYV?e-wIw?kMAOEMeDMHG=@dY+F@oo`|K>f z^vY7x))sgLWzHGWmafBnr0H0ft@F*|5BqlUGlmoc z@A%@;dr`ffU_T)X&H%R#E`aZzWbzYW-&)6T;H>D(I0-y<+i-%@Gw#j`J?D8#un21y zs^dpb%6|9ynEmTJ?68d41@s+yK6dnshL)Z?>|*vk65cn&*`ASrHAwtfim1MRBw{ZM z;G9d=xZNBjug9oQ zrC9j;GhwSHBm5;8ohSbkq%!rbL3(2@AKV(XX8s)Yyo1??j^EB8^u}(;0X03D8w&gl z?>^kH!k2LTj<`wDJD+b?mlWX6XNudx=euH%X84IT6VjMN6pCZS2x%P({1Or+n~ymZ z3KZdFSXk?)bKEDPX#A`Z6VHJ*G6~#Je*fSBIcgGD^RZgaS_wO+d?|D>*c)>dY4k@n99G=x&n9R5>Y!bmYULGT`rEu zp)ZE0y?G|a{yEt;LG7Za}mZVGXT41RD_A zP==9|3>uaU(OKibkmm@Mr~8LEeg)Qc>r}Jl+2oTUL)&b5&NwwhjwrH{k>`G_ewTRH zy&BF%OBKO(n|z3dHVn^()D8t62o<2b7Vv8jLsPbgW*$z4jFJMYL+MYaA&rLfLfQPx z!&GLU8Jr2pzE|XU-3we+sWJT^5oU>{Xe;j0VO8(O{jV$VjPGLz5ljdrBW0>czoAte zk(f#7iF*c_tj~OG);Zs7ekST&6snP*e*)H;PGy!Cd&dhSc6@b*c62xEQmgAopenpJ z?_-3`vW`pl-N)AF3SM^|_*G@$*$-Z@>0C=)Q+S16qxiv#eR6*`WSAFLVPbykyvBU# z`gaG{oRT)zBns3ibJf^b(ut(8o@#ebdDRd7cgd{b^|(xaePbbolfoZ_w$p$ zF;;v%CEcAG;h6UZPo8Hz^sLzDHC*#&;%mFP$4{PTzGQ#TG@m@Ld_mE?QZ#k3f5aC- ziWVVM(JNfBqqlW(wPQzb{VkaeMGNVTjWhQWc6)CeTLjx6^B6sWQqgPMmOHYi}S)wRqrzbUo^icI+tj<+y|q`%EOeX`Gd$(UyU=m9L-EIZdJ|$29TmL|v$yuXz`!aO zqwG!4+~2Fw;C(60&3Rz|>wDRe=$PT<_RV(`xK_LMjt1A0Zj0ju*O%@F$3>U9#_lMr zs;;SaG*mrV!#ZB5`m$z~<6;%%*VJ+RI#c`zC(dvKzh^saZ7!TAxXKp|y~P*t)xLBp zwPm|i>z$AfaP(GAiL9?gW!6NV)`xuCe5AviXD3rkKv{_)r{%Nx;r$BSM@2i z;Au_qtV4ZjcE;&Cp_o0aohB4BY*2B-Y{${I97x-2@-5^~2Ccz-L#iEjaR7h0wuihJ3vwCY3d#hJT2L9aZ&^=4D^FUVo+wb>`z z&9)NwgE(z5MgayC$dTOi>_lXScp+l1sqdgSp(gPATXjx1LPUY^+LdHGA>(=+r@ z_$qoLKEv2=y&b3EGvpKTy#%)fbQIb+oDAd2MDeu#M4WD1Tzy9Zz6Tn+Si-tRVSt?u zYoYKN{$ehoOC~W+=up@4wKklT`HxzC>Twb`r0l<8XOeG2QMxv(cqSaNuC^j0l z)-RH8D$;mTkMo8n#CnbBWAWUmp=U0mJfh$=9dzdccaXA&x;21(MHqH$(&S8%O!a5n z+wnBe8#?ui62kAG=nuZwNE*WmJ`KY4$VWM<9{D)DZ;ZTe#Cy0ue+stwGT7#Euqi)8 zVbkz<*x~nauu-FCo*B2^Sv$TBI#y>mzMq~vu@**B(AN}0h1k>d~&oVz|-Mu3FYly;Ts?~;AP=NmhDHgt1zN_6+*e5l+(fu@h(g;VsboBlUEInlERg>- zEWQD^TK07^&z{D5>QgwzWY@BOkDVPzC`?89OrpRSeKhtIa0TnO3s#mzXuG=wch28H z$c{LUwIh_@!MVA|x&DRM=i1rFbL|=S2{_>&XLkr37tX`Eu%pa{Ci#7x2c@$Eh^`+n^n&3H{)L4`GUf(^K%O25z#rY z3g=pHBAS1}(c2iSYEu^JUPQ?Hl=~@O_nK?b+zv7gUp8?6ecRbh)Y?EE#a&Fuhfnio z@T=Sg)ca*kdRzFsL&_p*MGu$%swP}M{FYT4exr4y*Q6|>&z8t@6`r~d*3I!}Hf?`+ z+pUk;5|771Lz4oZNDA}aVs5_K{lu+jHa+=pjAK>W6V*@d0IiYwfwV_-;EPGPi)?1) zd<4ED|H&OsR)6cGw@#_8Ldg^CIuidpoG4llOZ-;t^XnEJSq1wR-9MJ+!wTrbugq?I zRJ_jcsJJ*k$+{D_L^N04P;%be>x<^Z*LQclyokxZa9S?&u&#r_TtnsIqY|w*H(z(O z9SxtybiX8-o69YM~S@9*YtGT6Av44Uh%z6311d``5tz3OT>@n;^PDZiMr7Yi9m57v#YzDqATXS?B&h_fDIZwz}zlh$7WT3Ug z%?$ZCGNh#Pm|$*Bl;t;}nu@Fg&~f`oEuDqT{h?}{wS-KjGtmP)Qgo0=i!$&%0L>r& zQA_Wi&+`^rEN9*p$&K&VwrHU7lzmj|5Xkko12@CbPqJ)+#`brF->>ZwzqTUX`629o z#6siW!n6FL4{Nb6Z+l{OD_Wv=w6vo|`3J-=z(c~0Obd(ee*pT7k9~=j2XOa?H3fVb zxL01$TQaqxcbjc`H_Gk8H|XNZdgIEKeyfHTvk&~E_CtqiAn9hn>f*%&>wC4Z9dowc zdzP(X^!A%q*o73ky`R_%?bVfJ)_dJ?xKX1dJ8pi<_ zmV~SxE%{8OzuviGg;nraS5$gypBy0djunPN zlKXn6V}(QTI2@HP49u%L?>$jl(SPsiv?|?ET?fiqduo|i@dfy+SGS=yeHTJ2W`9xp zUV9m{a|MmrS()Br{!t2z*;)BTE%M;3<~P1rmHrgI-oTa?u3}7WX)A2~BpF|RKAN_| zB!@n)UAY2Wwrpi3G?biAxq7n?r!RQs{Kz$#n;c9 z3GOgc3t;|HY>o@L2yoH?}sf`OZloQ8k+X!|I)e^SfwKdYJ4Pj1k5gC`f^ zi;+uLJ+!on_B3`}U#k$}wDR{2{B8}E5_QbkTJ{Hx{_Krs)Cs4wLFL62B8z)@2L3!` z2h9;qw~Oin%$3~Cue=vjH=IJe?%i5Nt)gGhzc4^%!4jY}du6_~Q>$irom|!cz9c7V zuNMu39hhBR!>p*xb?gzpBHmh(JprfuG`La8S(W}3>?&sE3Wa@TB^^gE40O%)0}oMDg45ZFYHkL68XItzP& z9Y$&wSJ+>|`8jT~7iaf!@Rei!`_tD?eFF1|w1Byq={c4X*PS6U-zSO8tf#Ou@;@a5 z-Os^RsXKc8)Ngz#JVrho>Aag?2pf|gdUcuCI^TRE*3G({Zo6xzd%Noc_c2#&4O``` zu~+S^*0%&=qXJcXi|ZjADWim&oFNEN=P4>ARl+j&z;E73v+> zQ&`oE|6wCeV7c73_uDfZw6^_-$v?UVI}jwd-#h4IA!9nBWj)3B_zVKepsl3KPY9(E z!Rf(@-VfvI1ft03G!4aF(AzQo;}-YE<-#q-u9w2p%PNw4KTPI2X$daBzxz#Cd=$Nm zf@*Yc;=D5HJ5K5s#mGAF=1zOvZS_?f)*5cES%1sg4fPvp)^BVe_PX^OYu4YfmMmOd zw_zjMu(oD>!^Wz0>yT^1ZFeGj9=TzC{kl7DF>I>4V}s%P4QubJ+mK7{_)%OQxv}mJ z!);ZY4D0LeBtNfPf5k?_hC9|9>ed^UE;OuJ`|FzOwE)#nbIba*Yeu1yHETDnt=_nH zO&+mrsJnIT`mq#SUERiYbyaKDZXmPsW*6k;lb^3$S6z48S|h1kx3;Qbt>M=-4K*9d zMj55Nnz~s)98Hk$BPAO))NL?C+Jg4dcG4CRd)4~twd=@2v~W$;#wx>|H3+Rv$B12wYo*{zDomIf3 z;g0I+wG9n7-y!D(D74BEZg>@??$|r3tzS!Iif&k2UH9v?8#WoLt2V4Lk|k^LVpvlL zENU8%ZKJ#Hj*TSR8O5Y5(mm_f-bpH#l~gWV_EVUYEm?m3!o`b!Dv6KuYm_)u8*A#; zlZ|zChSfE<(?kxM49GN@M zdhMD!z`&F$GPkW?3s%}#hwiCgTeT6r7w*dqYh_MATFrXHuM7=&WO>!bx(0X6;;PjR zh*oDQp9N2?^7Wo;5cnY#> zh$flQ(c)HZa@TUiDp*FX+{#)BHd4YrwUQbCs^w6Fgros{A2*tZKBEQn0LtdZZXR5o zb>O$kt9NEG8$XS+eC0TE>%@J3c_+~JU$QRQVC&i{?*MfmCIhg>(T3L6_a&}_NEny z6Z<|f{qu(-ZHFw;zeC*TLZYFGl=5yY@yeXr|FCV<@%jG= z_>S+r|7;>*_orvMzO>(-w>M8+;QMy+5Ak2USEyQbd;OXH>$mtm+~OqcM9bU}|MaBf zjqmO`R{H6CcXVt{`sEjk`%*hnW8Mk8w6uYw*lIRb*P(lthvmt27UYWUG{2k&aJo_m*)r0z zK=wBS@`wa@;_s+0OqB#eT=6ta7#TKhz9Y=`&>D(+in}iL)p9&`;^h3a6p_o0_amva zMi3^2`ak)jbQ1BM$)8NTJaV2KA%i4g|5`PTD*o6*eTn*8=tv<_2wtM>XUa6l6s95u zbb_xIGCfhsh3UtL#h-UkdcQ&aAln5x1deqPY!^%L-w8-PgxPwL(g}(bt><$I384NK z_-s!xV#3r4(zQrl$_OWGb)CbUqua;xf!tam@jNoU=c!mAA;&%{3GCkCn zi52m~t)#Kzew0q7_+II_sQ-sxkDNw*W-ImC&t)IIjj-I?OoX#3wV*lB1hE)C_@+zt z5rMDU!%vL(lyZsUwe@&NQX~8=vR^E12I(@TEq{c+V+&e2Rpy4N@`#Jel*e7MOs8U* z9>p>bCH52p1URf~)R%dVktr0$E{v&KMo#^356#a~AHqcTyQqKJPp2cKo_>?82Y8Fc z!@v2r|6wt6xOli&%=AC}n|~J%XNrk}x`*c5J1*arko3LunbbL0EN&T=eis^_uPc&o zYU6?hoBnX|W9eh*k5X}HOK8iM(6yVVHb&unjeLxU@vvQNm%2yYlTe>fKecgcM;*v)##xUc7BprPL>a-oFFKx#bFb24bs3mYa{mT?d zu>d(s(8*goKlzs_Nk8$|Lo)x5|3s1*0y}y#rJhVlR7)Xn6ctRFGGYw)6L@A~u%l6E zhhk`mDA$GXk0=YImC|CO>Uuk){%sK8rcirm@l?We5q93@ZG_#ndEWok-rWa9QO0oq z|FQSNL3SKSih?IG80r8Xg6BfG2TFh%Dk3%xX21mqp7A097Q%6g-QZiCl@v;dkcu;i zHBh0QK;vPK<~T~D=;Vz#ra6OvI3m*0cUK*ovj6*o2R`@g^Xzla?mo9H&vQ5TEQ+!` zi=rx3G0q@HA}6T|ff%`*Buds!vSQxE7n3A{=SjA+#xjsfGLyw3RPQl8R>-_jS7-GS z%88M2r?szL*aNXh&Ts{u%xJa51Zz<>Yp{`YIs!>;23AEf;}>oA2$C1`jcvn4BrD;~ zd=Ft5FM?-q;5QFxfvpLUJC5V>6MI=NX0^`4WwX zri4c}7Df=`Xr5MP9?B7uTSqqu)tLD_lBN#SY$v(V$n!>QVuTlKv=6D(Lw0iPbHP1AAWk)b$awPN1WlU5<^rNzBAInG=7MMX~|R3y}= z)A@#b;K|{>Ivq(lPNgB~^Bn#&l4wY)cmEjt4WI4H)oWrZHwkkpnJJrT)`?I#d z{7P#Y21yS8kxv#vaFF2yGGjsL63nD1N}~eGBt7DVgt05)JW8h&F(-x+ksC!DP<9!7 zNE(_=S_-CAnnOWY4j>jkdZYs=1UbQ28$h9W6qW-}N{{tvNNJG!uDG0zT+Et};3kQq z1d65=;(FXJk!FpIPJJj|z=ezW3sKd?p$V6rKXF;9tr(xw`e@>eoMzR6-xs--tvsgO z^ZIp{bn8a??vl#0bPLC1$P*pnD%ZMP*j1rYIPz0=7kuhkbm^wDCoGmCBekmZL5upH za=P1^bKg&Cy!)^1QLVS9BKU)aQwts4osQnRrtE(3&oa35BfPMKC9Zk?P&KP!er43$ z-1CpyTNc-bLovzxU%$oI!h;}Y}Vw&wB2el;GH zU#rgVukPr`tiHK5#(K}Tufyf+{&Bw9{&xZzx8yIncDQ+UkNHu-@_4Rly)Ed_X+KGG zSLv#g_g}KxVk!@I$Lr6?GiJPKU?2WV=XY&0%I*O)&gTK@IPsRzW^{*V}$?! literal 52962 zcmeIb|8HE$b|+T1`!?BZ66_`^X(*BsZ-0?QksPW?N*YSkmsWS{id1VP>f4OIjNUXw zanzJFB8TLRsCk+ve^~=YHrUN#H-OjnA{)OAka!ot%Xs~R13M4!zV-UqUUJ-_ z{56CsCN%MT=lbURF1H0FataWm`v^|wQ;r|=eDJ6 z4<201Lkh5=mnyjc(m_PsbXUMmQy1t!K#~9SwaQoo^6j(qH=Vg`LgabwVRh6Vr>00{j!3MKSLETqI zd=?}SmXY7;11bNhViyD2YTnj>&Symsx)p?y(-q;uQnVzXIBKrV@ksBi!2=97+Tn`~ zDxbV8ROL{W?U(oa5|2%_%?gtNr#d-$(lvX;> zHt3u}+6GeuBL>an0UV{>O$sDI4t7v+?rIu4pur zJUh@3QWC*^;+G^&5lV0>J9as#8%tfPY`-_6o5Dd&We8|FRX&xy=M{AFVJ7GEmk)x% zqC6eXUY;`X0mMnydP8^;E(Hr^B=7GF%GNyo9&2bG$F=~6 zhkza$_kgF9Yg|K}>)1)R$YMTkuMU`DPX_*Ddxo2U*^G@H3sUQ_ ziDylyI}S+&6F+3a!x`ZZhX5K~| zaK01XF=H0L|Igjrb(G+~#TK6v4)M=R(C}HG(Fd^cYm*HxN3CAyv63t!#_2E$iQJLe zk@=Cifiy~mH@3>H~{?$PlN6eT8TfzB(At1U3Kkb+D4(5#LY?S@-*WVu7 zN{k%zE z1Ww@T{L{G!GngKMbbm)>x>(J2>HrR`wh1L0^TtF@JMzCmA^OV%l>5`e)ls`@j8ZYHSba9YN_-B}+fOL+M5c zLyQ3Wx2EPZf~GlmRL+@A)wKl2)TkuvRQ|pl%1?ovv!>+YP*>}zI@g96=Olr(V-mW# z89z5~W{Ngnja&qMr~}a~Eba5HWjw1Si7hcSBw5#5ZpmmPeDt-eKwag2@IxJbg0x=NbR&;h-#XsU~HrTWCEGC47lCsjRez z%c^aUq}r)UV(c~!pMCyTEsn51vB&Rk+-)SM<3IS9<(!$;*mi+G`dNxLU0A=i@wUou z1UD+~lBCCGJ~A4ZENL46dBAthgk1}QTtl%#ie_KIkxF%w&9sKG{Nu}wdD0t~8&l4o_r^|?tQu#Ws4TN^Qxz^SFOFX;b-8q0ub7s<4hRKbED#n!2`s{*vzpl zcZ}A#3dcc}w*?_Cz#$xk(nYE%26w*3J|5Kh^~Nqclp{B7gX$JwY)&uSoWmL04cdLJ zk&mXd4DW$$0xL^J*B9jBq@cD}#GLBF_#De;+XcMb=)9GNl1aw_@%)Z#zy;*+{9$7& zLM=$3^vsG`n>*4ft|+-HEg@taEaujQr_}v8As&M(!yzS@$vI=+7FsYjOX2XHu7Eos zshHXK3x*Po0Xr4YXHe%w#NLN9cR{S5Vz9=5kGtcz?v%gySF>B^H5Sx(mWpDQ;+d-) zyTfsJtOKFe{KjPC)ZE?5Y(sd&X9ZEvrw0)J^1pEGmZr$u=P3l>kG^NZe?Hwf&63I^ zfAUWeI2oZUucPe(&|WGKpqE63%9(P_`mVgbFMmUlS6Q|*1@Fk^BbSAiayW}+6&3~@ zScE3N^1^vON_UcMq(cv21*7593AE;P^BfRwVDA{J;0;a>Wexy=l0Mr|k$^pyL+jpe zTDw=p{|%R#1B|~m+qmFnB4Vxf%>NGfM$pq-)oE?)ZIH0e)9z@4hy`|O>;P6|&O((L zN(dNia0pU)Do*)7HL(qj+~c+5`idf+Px#aGOrD-QUgNmodNO_G_`Ij@ml0vSHa+h( zzngoqyRp3-saG+lSUV}Aoc~C~$vD)C4o{#?Wmq;zzrwMa*btDz85HE}! zx_0<#`xigg#(BZX&#qP41)E-*-I~=jrLQpK`J_K!4+_&J>9xKRQ`va_l^!wRpVdz= zrnhXJtVfeAAxyG&aMtvN)A7!re}Kyv7Jp0k7RKcn^pum(u^^o{u;52us${HW&a;RK zjmD5_x>{++mev8)o*#+R-Gp=JXmt;W8Fvt&X#_B-gHvvysZ0<8piR0lZ$4Wa0>*9e zqxLNP;mhyof%_l7f3M+8-K(qIz#112@_Q~j-HUl&{xZThI3f)%F+EVpzs}LbJiTyk zjCt z2G9>VE*^IIj<#lddU21-aN)o?16QIh095pPOZ<6D{0ZVJpVKSYBIOQXYA1|+2H=I5 zVD9l~Cx?xp3IQLo0Rq9b#|4QqrOi)sSot2hdKieb$LA>i#qlYF`k+zTWdRCf@b;&SQIej;Sj@Nk*Q@aN3S!}wf)pv*=VJa3cGfJbSXu7 zxUXy(LhMUwm(#3Wd7=X;)V$-}x^d6v(oR~pow6>te#+m6y)uY^leM87O>j^@2ok-s z`>7C?E0^Kn_H+}p*pXvbml2L>jk;Z2l^*)>Klt@>fBkM95uZ7e^uGKj@BpC^aV(Ts zp7Qc&8}nL?(q<_uSp?Y2@gu`m0|Jm792NA%KQd}sMWm!>lcYnnWHW34Ei}3QhNc7E z4SM&?U8pwa>RlF*3*H=Q6rCf)%T4O3&&)%hP|eC4XfdfVQsL@wfQIR$r6H;bz_@eVIfOtz^i7ZR z_NmA88Ec*#aan+>C#zO~qlkMs#@gU6W+(*5gT5F>XwAjTGQCRe1Q75X7ZS~aN)f=K zhlikHUwEgYees8%zt8pnI%>+}BAPAsZ|(F@f)ek8WAh%sQ9{xLP|K#wOR3uKHKgWr z4XVw|O-^xFgF>;=B)>?PW zG)ao8vN=Dgb;w<7Vaqk9r_u?;XMHRZxNT({S-yZxyMc42q;+mfyX^Oxv>4JTAFXrN z9|X0zXC$ji9{{5AW&cgQpTYXbPZ>TSt|`<$_kl%!-#h{Uo5y@3{$4}bM2ru4nA6T~ zGgmZ8i%5qfCrfNB;+I)z%}yn${@_+kT00O*-x1( z@BoEyL3LGu69I4bC^NY@Ak%P^pc{@-vVQ7~Q&U*O5@MXoI}qB|HfxvpSf+qjIp)Ep z*Pw7s;>X9hHVLra*uMUOy_fmsOjz64mNU(?czkTFo2myi5BtcpkF>7K!qXbE3;`K| zzx!LwrAQjNiUFtN-H{!y=<}&c_J637rROTiGp;Hes->FV`@j`cTin`<_dpBRv9%IQS%eEqlgHM{rgPA4>w5(`S<3^s zuv96buQogqorHVz23zG`djo6C6Km6B0H>wSmYKE_sg$Px2N10EA_mWMG$wsQjXn(Fc%se001`WsJ_8Z`+J)P9T`+7eWzo5 z>%EJdQgSVZeVROQdZUU^D~X$XKqqZdpEB`K+>9-f+EYt<1J)kbF_#zR@0CO2X?Kli zDNcmsp-INuR<=gBE?l&6heW`8zJ%;{O8B%PrUW#`Wi_Rho^*0$<`j5X99f5Pj zcKF-rz|fHHmUizzK8o6{2WdvlFU_>>*x9SpC_`#$1q*~~4=r?mEtB0p7ag&N zKc3w;7vuzz$@bYpmlL9yY}!(F)#;WzDVw^R&{BnyZPSeGEOy<_GAk7;A@O6Fm>VnE zF?O)#WDjm@!KYx%WCwMr+*!o3{Tk=MAJK18a>j~f_h{-<;KmTws7A(P*^EwG1a6!8 zKJfk7Zqvwb>y%E-bsM?ex&VW73WYgPZiUg zS7V3qNV0J0AUR$qXi`p^>S~?y?LLIZ0D(U8;=!`S?CJ9WqQ$CP6{_s2fJepL$u@rsik&Yd3JCe}I_kpXlPV~OsDL6HHyd(vC;-!| z+#ApXR{*KNzu?!*@ZzK)K#Pw;*e9XZ4*2@gnDsTCzMp`=BP2lUiK8x-qVJ^C zky{7ZlGmM_EM#g(x7YwvSf4p90TbX`CxXTNF`p79)x6O#^)%_}(DmHcrEHIJ_ z2mO7ucE4+zktFFEzy_f%~@Y z;CQ@$--lxmn<6U(dx<8e^6&ucbLM6UC^PK((a-A}XVRA#hn=tlYbQK_vz$dZ^7PRr zbxwoOugA^sdARW z+yd?lrEQPfHy?zWoaw9!##DA}wC?wC)T+ZN)wV$TZr>&C*UvYS`t=eiEjHC|W?B^+ z@a^?A+a|#09Rhk>V3UOye16WsFOz7aZq04lXa!k-M5UCR%+b|!xV6tix@HF3sOOG3 zcmeEW<3S=2>f-@iC^Pg>v|GZTOuiqlMeL2a8azPs$qH>2kdt7~+3MRBplX8>TUr`O z`Un3deEr;8hpIr1kjs=)4=E+2J2uWpEE+-F&U8$(Nz=BYXDXn&rZhE!2!M?^hWjCj zshNp6a@m2VW(s(}fLSiWW%78$tAK`9ly$C zOwSoxS9Ms^K|WxSA&Y>><}W!YZl5M{DK#3one$`j(7_4IP;aEn7M+u1pnz{7lJnE^ z%|{U$SLJLTx^PYn^&nWBzMv%osxi^P2~ZEwti85qcCKjL@U_K!hH}_ebba zygx$M-*G_z${<1E%`%WF8?y~Wxu57-IG^sJd5lyJaz z?EEdb(kl<|P17>8QcY`YAHDukoIZl2HeCvVIr%(pK9TFAbL922<&=>j>Yh4K>s_Ihj|0 zwQzrQXiJyCIjnpf-Z_5)d$TE=At(>K{)B#R-^M7wNM{aF*_SLsKp$AOs`X}wg++l~ zegLi?wnM?~cIr@+DZmq6zkdUe z*>NZ;wsM#{X8!1LXC z$|;a3v4$`%wSaV3HcCBB+n&y?r%0NIdwrBEO#sCoeh&O&i5K+=14EHn!q(fYOa9st{J?qm9?3i-wGSi5}|8E>Qx-+851e7#wfS2oCN`}!g!zliY3biOgqy*(k(HQhfs^UcXWG%0N*UK+WHVpF zRh7&5`*ryNFOZJRKIfx5lq`vt@g+s&O=~ICqx>@b<@^VR*3Q>A+*$Uw=dZvAa9S%1 zDt>5xh|9c?nePa1cO33KoTI|*WElf|*3%e6P;R6=c78n1N|I+f*S>BscRW`304~~1 z8;B3dIsUWgSumBqSE)2or~9f0s1AX0Hb6TT_BkAuG5zgnuU1+AU;X-BVdz^67>krI zT!j(2r0}RL52W=p8x9}dm)I9RGG@Tqhj27^79Sm4Jk6vI^<9bHIDzz8sqw6mIul$T zYAN?1!SVXxN1;OvI)itpA-r?x^)~vp=`-vunwOP^R9aJ18n&Z*TTjMJPsVgls(eqQ zbFH^=b6Y($GXeHZ8r;LrdhY0G6&!rbJcKJ0ACINhMb{vxkHN0E7Av`auJxk~T-;#WXn zyY;R9Md5(Ye(>-5H?-6N)xJoaa^Ill z1n4M^=#LvaGi+=pwNsXq6qIsZ+Z-Ftz9z>>I2#8#aIi|N6ll-TFglA`6}t!;Fq6~M z$}sii{7@D5eBt9X##(970-d%S5bboorIoUq>M5DsCz{0|IkCbfcI4r$>GRJTUY|$A z9UGyaCmzpltR1d%m^6%u&jjDifJl1fB8BDD&`}YReo#}UpbKy#3nSLx9eIHT#GbCFfazy#JgKRGTHkPEjtg~q&!BH(B``g}H ztm#r&*;iTITVQiLIY9zs_dl(F0}D~JgDdX7B{%`Dsc9-9Z`LB)<``%swMsJ3v>mvz z)iIt0mjLbl4G;hJ@Adi;eOH7!7o>+}kyD_%TMs1tLb6aeeWXm@-9r7GN7yr^`lZ&j zR#H^3#)cK^>2?I4I2~2V3uE|Exl!PVa~SPwX|(${)bG<%>gILP+8=$ zuX%JCu?wIHd{v`4Z$zQZecrU`( z_(AyZ!3~-!C{{StnrG5(NFgsxS|DROk=qw*raPA){M&ObBEc%>$IB@t{x}xPb-4K> z9cPAWYu&Pi>OX)~N+m%01Gvyi<(00!T3^!(hd4pZyhB?lb4^zIBB?LKCoj~a-DuH_ zqQFVQxmM_S^nhCGE4f>&C+$9ZzzMalWG7RmuE7cD-p3ON*_=lAyseW}*oTtybq+s+ z({yLUlIP*V7CYXl$>7fBLYIHQKTzA(G=WN4RRl};8tmwo-vt&ME5LsQcO1?c_u%iX zz12VfVCf~h!T^uw;Za+Fn162V+iYC_1umod#XLzFlRz&&2Ynvw0j*68H(HXeC5q8uAlu_TYoBr^( zqh*6_N+mZCu#5KY*hNSL$WQ;((>#3fa*bz(CAfi9e3a9MzM&YSDdGs_P1mXRUcJBQ z%@f~!s2oPB)$=;V&wkX4}+NTXlK67=veYH_gr|bIuv;b?M^4eRM$L!>7$O#}|P6jC!sczeFW(X>$v;yB$a#e&*%*ZMD#P<-UAG zlHu#g01^O&rNM_)oG_NrR~srurBV3-zk7HAOzvGy-&|;E#kooje!a}`=dvpR2V(K* z{;@;A!n)gPsfW!IT4=YD%W5ZTsnl`v6ukS$S_N+bZH|sD;+#|jWVALvK6UaXB)YS^ zYx5S;y&cNs2Dnw$bS*aAq&4P^EAIyD;6A#{+8mRuy@p5t&DyyJ`hxyCOBks0Xv>5u zEsFW+TA``8bbZ|51Dv>y@f1|9=r}z4$u-cnTB)PC?yzUL1CQ1kZ7;{CD)iNoO;;y* zpF8>70xcz1ho{f<&LMS(C(sHD12FEKCZ+1a`;+gMtuFW>-)_=K-kt1(w-yTHBdYs8 zU&~Mud7P)Dbs*>MyIfZGt}w2OPFag#1VRNWj3J^DRk&xtm(v%vt>&^PD{mZCd9C+nE<| zleOPsq+EvQo`DC5Ke?ZbKWyE5g|%~*W4C7DqBSE5aPiVjtPyRqZt*N@P;JwtzUqDb z-KN*B>0`@{snTe5Z#$L zwL?uCoV?#Q%!aj-J*jWbfxHXn{_uM=ct97Ae*i6(&t??r&hh){j&Z6F5T`}m<}-{h z{{_6H{>IPirag1r}%S0DX(XHdB}P5QR}K4b_9|E?20Jt(DF;_h_?Fq zRWPp2!NUj1%vVlMMSBVqMRRbwD129me$IQOe_OBj5h4IDmKu&#y~*3a-ShhzLXC5c z1E>lKDb7UtSvVd(1MP@kg5TCoA#T1DMFK6G^|FIXb)Pcz-52lx?X?p8F1W9sUL=S3 z(Z&m<2XD4@`EN9Jd-Bj|F9>lVKQGZtk6hJc>#p|1`{XD1hhg1s0vbN7kEMX5E zr4R_;iPJTA@2@f3Z0UO=Y<{h6*gfSH_4s^W4~p|rOoB}n*x-?JICg*VA;M&AaKG-= z>Q%&IvEy|FWma)>vkD)ezjC!>3@84buZ0TT}ST-^Mu(1)L6+aobFm&hd_Mp%=0ZZ7?H< zQoEFEyI}}#rvxp&T%ql~>>Z6h)#(QpUWU>?7si?aQw$xKDFCCjn4UKfpC;Y(-xSbt z5? z@_xOt2XB@l;(PG)=i~_6F^QW_jZ>js_oFm4wfSaB6_UcG)o z0Zu@)dNygVIXdy@&BGb9+st-Hz>)BI7;HrDK%d|}Yx{DGl=>78e!{OZ9)W0QJr=O* zq{qDo9R-OBW_N`tJcT__G%YWNi@<``;?)=BkzwcDd!V{< z<_7lgqGY&`__%tZI$rIp@<`w&+Th0>COU~j@aq>7+R$!oQp$puTc3}0beN(6`n?xs2giIPZy;jRmP_x=eDM3gS;e zu$@7@?%o@cvuOzmW_M};e=_7ED=`)`*x+uN2`bKOEdj2e7lQZg=Mbay6t(qGwOn6F z$s^<}#O+J8;l#%pXVLVY9aEUESuM>CXW3kMS@vN#vj_td(DHVa5J|Bp08NQTSnagm zi4-DKFI6hXE7$Q2NoaEVu{U_w7C?oPGNfbolap;#+Kl3Q1!G6y2btj?t?bvFrSc+a zF3rw^bK;aDu74Q1GZw3m*G*9V5xoX8)lJ0VcdC&JO_sRM>FlrA_hCQ+Ff|Xye#z8+;-7sVoBHe+Uhb18^!6*= zE-4UxFHH?_ARmx`X#417J9^=cH_4Lz!Eb|>Rwg5Gso&b;GAMvu>y~8k512yimnqzx zk6|D?!PpVLLUuu09-kK#va~eX1$AA(s3N{k<1!^uW)5(U%m@42WgBQB0Z=}szb<(X?FvCA`-N7xMqG@yI;*oTO*ZG}`wV?6!GzfND-a8@tL{OKBy0k(8*pmKgZ z%LRK!$mINa{km#2HHT$6%rPoS`aS4^nTB?2qou1N^KX}jV;-Q(nln|{Jln(B2D?$f zwn$>kn#UK8VY<+>5xEb66pioIc}8@euyv9z2mQB}#W?F|{x&Zg;iEDn-dbK7DN!f2 zs3Oaptw&Rc+f*C!w8&Fk6hiBhvj@&r*pNkMJmH=}5ZJiBWhhjeSL z@?#|1Yh}N3&Qo6NQ|oY8BN`=kXPhq{nRWb^;atSq@C=fT6f4`=( zdvk0}^+>WeYk8mv#$Na(C zzheG~xy-B>a#ON44 zq3`LWOjXR+S#C6)_-tcL{{;SFVGI1w(nD)bYt7H4&iJ<}nSW0EL$w}!m$H5ANl~r! zjWxy;9fs}EF*lhc|Zz&`n%g~l)*JB}HlHgMb z;78kA(1+!M{`J5)%G%n_rkhYLZ3kO%)#sV(0wlD1Sr63h?JMKf^i;4zy$+tTKOe-e z=s0x+1VB-lPTRM$=LO*5`S3nsU#AC370xHfU^Kuz%}%|}OM^f79ujMudk|_X7;MY~ zy~aHHmp)DdW4axe+%enPi}uY~+s<8#^Zdg&XClTAn>hE=jA!9^xlf)FLMnf*jVatS zw`y{_L0+!wcC;c2Xx*LbQp^OnDA-k;JV{^G|_; z3{Xza|KaE45jcvV1YVXzeKCweYw5EMa%E8VQ9o~N;T0%ZZ3)qwN9`L$0Wr#x1N9i{ zHCoX(-33LR6p}#(AbH)x7c3trbvU*^N<$RTDl55m(gb&Vt#g%>AU{*cH129bEtNrG zfA?b^40CebrZ%qo{aQZS590!CMyo{AdbNw%2CqLk7c$?-F4iysJ&}jyTYI<)Jm-@V z_83QjaE7*41&xyD^alcSPAK15P+R4jh2OhkDE-Y|Jfk@ZAU!ryT?074j>MvT{VY^&Whgyr$R{4!h=R1gVf(+dfF z!)U*je4^(P9-DZDb`4Hu5o(Gfp8HYOkT7wccNk~69fnpgGz5TFtxoHc2S=psJ>hFU zb(nsXe(E+Q8R56@`z2i!c@Ak94L9%qqbb52`1(ZCz&s!nwv;W&U_{&BxaO%UdNK$( z=eo3JzE(mxnMBVw)Lay_Mt8J%;ZjBBh>jMsGWyiFK_@he2=5Ia&Sa&u3ff^4Db&&RG|+XWeb^hdOos^W3_UI#6L(b#>jWA|Mvu zFOPsKi0Hpu4&61)MY%896ryHS9bp^=6y6{_Q1g{B0z zx(ay?AYm@2Uy=pJA^sfB9wLAO*ni|KY)K<6%KLS3 z;dS-5zNUJR+ypI@eMM2&a};=g@~FY~R@_;p4pZR(wb#DV(XQr>kA5TKnd4$LjGj95FQb8t zv*^x0dmF0ZF?eQim(BarP#GYW-~^gq#u1u)lLMTMvm{PCeST-t7or`PC6#<(t>!C) z$cNpmjy#1}sFU3}=iggdLZ6ZWt2vO_qcWlI5sI|^Ew5yWnyKmpK*@vbxGDCV;>6z9z-gzJh>?e!2x zVWGYC2eEAGtloB!r`om8{s9ccmM-ewNC4K~81svD3P&z)SFRkmD87BX!3jJY+0=1{ z&q1p!Nnkd>fauLfm?lZh{Kr7d=^v7c>{o>`dWNWgL_t~$vp0B>gLj&MXm@QO?$x}j z(IMNprAByZ)aJWmJZ5s+*W)+1?+$>oUqOEaucwrNz6U)1Wpbb0S=IG7}^&(t{~BEsZ03f3#qjrzcxv{GY`T+GnwAO57np9FqNIl zahoyN;3VHz8c}HIxE5Z1y>Q{ z;zG50rhmA)6(bv|i-_C(Ezkg%FAA=I2T>aCv$n}#M=Ts3g2~86OQ@_cR-n{TP$QIz z%Y-c}tchkkrDY3g>!3|WcrQ z3uzgD_$4pyXqw-+@6>OcH_sQ8Qh=(Dwk${h(ws+9w(|yu+~6-jyr>z|(|@L?gum)K z*x(CEN+H%CV_ra4d=@Y8gjKM57V`01lI}R&(*}D#JN~^~DNHooyXLtb) z@aepS+{Fga!`YtNatBMJLS;I|O{eI0XzS7OBx`5Idi$IV*9nqYM)lRHDks3|D^vYQ zx@xotD%Ul@(Q8#s0$mQpRK_S?H6*daxnNYpCGRp^_0@&yDcv=~(Va?HS^5T_k^%JE z98a<1yD23gX~OHaEOwayrLU%|M-h;Cx;j;V*yyWn)Ddaumpe7KK_Tk-l}dGbacO0# zy0M7(N}N=U-PagrCz|l~*(sbAjVRz^eE_b5ejOk4os33^&-zOUS|?bL0D2{Tp;uD) zrufQ5eL{}TFMEA8%I$9a3HNS**Al?*piv8y^d}E`Ce*L!cBok-;MEZLZwFk-DWK|p zf0+S|4v?Ix_@%RNSI_B>ff~+$e(+mCivFUWtN`99G@n?H>8*G||8m4g^87L3Ymc)d zR^iUmOgat`bBM-nPg2EzQN07x^Pyhq!>v#UrZFbX7cLda$@}{+pd`Cf zNKW#PQnRhL<8o3};Ft9gsI<$$p`s=F=53c^lT+*wwb882k~!kC%~4Q6MAngF%bO!9 zUmf+o_?cY%2{}i?p%}k2G&2IyEz|!%OHR?~QVN~^O8Gq;oa}>ijx&4EoY}FI`YskA zH6^EPN&Wby!rmuw-pmhkI1Gt~k8#Nd1TXIAwS-=bmh5sslrLHg;qH%fIDs|Hcb7l` zcF!uYs8+d3@+Y_Va6kewwT9keM&DZr`I|R?JSI0&!k0dzG_KMV+*%l|Eef>a=sF$a zR0rrf1J4-4+9m$HYdlD)2#ZPWIwR@f1S83mnWLE@$1+1&zd*9YI43Qo+*-nv?T&MJ zbGQTCaTH7jRGxPpU$g?q4>2kuMaEQbQ@fgerqK##|H@WoD2ozHA4v@vGltF2DVouj zVZGZL?6owvxjy}ZAqutiHK*!dkBOvG;oW}1x4$qU(%4U%B_@9Bk4{e1h5ns@Nq(|Y z&sCs78}O~xW`L*grHC+ddK>ZI2hKQX+xc1cg(2-S)dw06m?Ll>03LyUJP8c1nSLNg(? zr+~ju+#u;)iU~Q-cj-NNm;Sq@Oq+vCB+gWvlNp{i7^BjS;)SB}oRQMpIJ7_UpKvNQ z4R3G%^M9sD=RSaYJGY%fjyei$y%#{4=K$>09a ze@OBOcgOX>lL~H3u zLtWFwSn3)IaZSo*Q_3b%s3Mxt*9m6KZ%4tHwoNb+tq7rDQEIbb(Uc~^jOfW3a>V2D z3IaefDtr@lma4mxqt?3fcdPXLT(EbCh>U84Ak68yf#pOsC_ok%tCVQ4d2OC^j|a8?}>L$ zwjWYQz@eR31PVQy09g z7JsZ)%n!s@d7)1kGdpfVp_+2<;}@FV(^HA~l32X|r2i5X7eEOZHK6Zj>X*0)Q-|>9 z^BVv5=YYQCgF{myqAgH7>fdkZ4L$X^G6>I@A+Tbp{}N-=$F>@|RG1o@(+W?*6kr0y z!Igg2U}6ZG;`b3_De;D0TVDp8Pl1)w@H%m?7*K)4j0VSxdA6N=CReI@pQqU+H~>p+ z&MEXN{`NVK!?AokD9QFpwJ1FBO(|wM#FYnk8PukiqCbQ|uk9Z(qhkGYj`jvo_)PPPEISGZIlKbk zwj;ixWusM|M`pmGKgm>Gi7|qZHL*^2B}=t`*J}P$c+wrRIM64 z&rr*N_u%Z8qyzr`;7B_Qx|PFtnikf zyY#%-;{jdDH}vPTFEyY3jgUmX4S&;CiWo0SXmiI9%?Jtpc+vIS^mB*yu_K5l#=`OW z0C6?a@uOcu_`m&wUm3`;N*kX=dx=zfYoEUd!~JAgvHpG!PSUWp#EdFxqAHOjK1%Bh9B$7jF@4D^tDSA5s-H-3({q#ahH9OuvDkx)YsDBEJ#~k- z>`#99`8?UQ{`5g*fiq(s&K_G-3ulmKMb>$d;U*lQQ~xv!3w<4*o4&8@WZ({MP4x~e zP|ATjv=GJ+Jh`XSfZe#qI8#O?6hFnT5-46ls7rQ4ddBA_Z5xECw1BgDTNfggexbmp z;suJ5bKcsoNJjM_AABnfDJ`%dRfGEEs-qC})ZyJN#NR+Lq<1mu2+-?rTKRLT#4L9m zeOIb?J=Z(pYMjJ=+8Y@;H@Z*VGx~>xfZMsA+jz$?0bS6y0{W*E58!4K<ysL8dL} ziBvyijNhPGbWtb;kz$-PIY1TQ8GCcj#D#d%zIrFoz}JnS%q*A-wY<<$3>biTF1Jok zVnVyo+o09z2M0p&+|z=<_X13iqMa!bed7A>tn34#Zg+fPTA}B0b^Si5*B2!Vd{aFM z90gta7gX@q0D(8mEK06b3a)3}Bu$_5Y&#e84mb|~J80s<`GD!0DE$8aT-^8-wk+@xz_aNIP<8{(igw1H#~Zn7$c(U=AgO-gmE~| zCh8em!x%t6fk|>5Gu^;G;Qe4J4G%C#>q!aDW&`vG-v8lO92T**QGxHpK-`H<1RDN(=jq2e5U z@i?~@rPF!%osD?C)~M8hQc*$Jxe{NjtyE|YD{w*B`6T|Z_N4LM@w*LOWL9N_n)(46Nl2{I=p!D zul@z%nHcRWtvlZ1Jgwgd=pAAinOGUVdz*gjc4lgw83D8&C&r1fD^0#wFK4H8SKk-^ z3Zbj-cj9q(y05D9a~&Z9V9vNcv;V>8L)Ab|lgd^-!t17XD<*m3!E?m{uFhtKrUv7V z)<3%npp^72qptol(LlOZnG$2a<`{+Rt6YQc=-<=s8oxp+J%(Bras3D25!J1dGVP!1 z6+Wd;N7@rY;19>FFl(3aTsbQVuMkvLKU`Z}U-mv&dH#5LeQkZ^`R0bItUljddH(UT zy6|vyeN(M3uRPz_TzvKnTx7L?GU0oknA8(^gsJp8l zdmk>o^q#N2Q17ljKeFkqfBf8AeePYq;5}OYWMyd?MQyA+e!l!DC|x~T-dtYVTz)j6 z%Im97m!F3z<<-^AXRC{kme7&KXMeoH5u!qauv(=5w3H4%S zZQ0vwEE~DctLiG-@2xLGH7~svi_bT`%~kd8{KEXzOWwr9gl9y0u?UGaK3-Z{-q`ry zW6g`A(7LK9V3Ar5EuB;WL}RnFq(%Ib5qxw`5-TzNd9!sHE2 z-1rbJpHPiucF2T!cYXEwe~A1((IQrZm8le|J3<>9)F9G|A@sA#*!Kx z&}fHBq?O_xv;%n@z2BPhR#dXr$*_=8M=v;UA|)1g^pB>-OA7B!%D?u66ODGb zs7n`218?x}d4~UNrcM+dAMfN~zsWB6EO82c!P$i0#lw0P?x}fs8E60v{WY1cfa^c! zs*m6%gy9NT1%cTQ5%Tx~;Bo-?{a=Nb>$#}nAH4rTYJrKL!rgij<92|%rL*U_xV_w% z-*dO(w{tEQK#rvcf|B+22k@RPFL87ADT~O%z5k3e<7>FISs{S}^oIG_XWW#T_MD}} zchV-tsWu91{l4G($1f24^>2Tm;u|cj^kU!8NlK75cKVEw<17e3LAjO*{K+p#nASB9 z7V3yrKB9&u+NUImjW9dI>9+eHt^L)nKBwS-*EJR2JNA}D4)^qK{yukqTs{FG!@cVH z7JN2!KbvWKu{9Kx;jW8EqO) zE2xbId_Y8=*BWDqlp^dy+e?((wu*{j{1rk|53o=d+O9~Eq7Y(%Q0h#Z_UWk|JB)qh zefSM~N_UHV=!WtrCd;*9&>iiRK_?p0Hk?^BF|b>@3D81JA9+F1mKhC+NB(6pO0BKRM-y5XD3X$V8%|)@P)H@t6pJ$l>trbi zb|~jH$U6dyFo|3?zP`kRTW8L^+_5N=ou$2|ckb1}t?iOgi*(dA9p$F$Uh7^vxOH&r z+$+lP9sENOKz~W8%vxy4Fnw4DlDWIZo)de4WV^~Fm6zeis$NNznE3VHuv7uXH34PF zciw#}P3d?0997p-DJE8?sYn#oz*d6V+|Jjgon)t?w6%&y;bqFE zyn?+Z_VpgK0)D(ekGa1yZ<}~*J%EY>NF&(*QH$sk+QK}7*^q|~orKEA?~KqrW^c`;gVH&1y{FW(!mAk+GJ~|4XDX4&yuwI}#oLsd!9NviYg6%V zrm6&Yp#razzOL5PfC%KF{;8LMg7W%D%obZE{?}joHKYth%RwYy7DU=3II>fIfmbSa z@}!DQpM+pDY_D=h@K68t>a&?lb3rB5777dAEtLp|+;^IJ--`;wa+^eY)g0ZHt_fjX z2Q9mY1g6<;a)Ya0(={{Ce(&eB@ZgHYbT@ye*k7cWy7v$^F#xQMojYgZO?o2(r$CcNbf&wBQU|H7+G6^j z)>BC(+=`&0awqrbz!U|_=;;9!r=|cfv9pKHpR=v;klnUO|7w(G5!RuT zIh8zj&Lf}_6QBatbN+ncOg{=cQ#gNKx#!Mhipm{0Juz-&&Z)$ibFp)%sCcIPOi{JZ zoNF6uJBP7z@?7%VNtKv6o1hqnCr(f0V578&KoVC3c$HCZGLsZz{hxnRh#bvS4`)X` z9d76|;z!z#;zId<$&an8WCvUZ?hUwuaEIW=;fip_;ZDPq;C#5Z;cmmd2e$z@3Nl;ogS31$P&&4)+-DDcl;INc=*u zzcXL{M#G=~3~E1511Q1UKna>E6-LoU#dCWZ+agre*A1xy}kV5DsDU_Qk4xC)Kois7|O!;BTrK{9i)+O?4cUX{5fXPQfSsCb*W6zKmEA z)JGcs0MyS=no_;-x7AJMt6Ms~sIvAo7PEtZq<=dY+sqp(UhwgLDgDk#_h+J8*m!GYgmg0NzXH2V@3c!#@{$+;-b}8(1U#}2q7$8()d z2|bj146Y1UeaqYm26g?2Qz*6f4lh}$v%7GB^Adb(NJ9ABUpZto18P!V&UW42d;r=y z9N<6x47|3#{uSQU3h)6Xz*zn7{WIX{Kl-n`3ajJ5c;gP(y!_Yy9fJ4%!!N@BR_ zhrFVlgaaHgtKe%R8XqJA!hv*s3DLI4N0077NUJz9lO5!x=7-~N_oZfT-r7hlGv&!A zB>wKt!W%aN|L@=bwb};*Akc7Xzx& z2P)}&OC|TioqAtS-0+9%7P}-E{EkIUd#KM=3uel}&u8?@PiRQFfv?mmF7>|+2NL`O zUXjE<$JP67ZIaHr^H*=*ENi^kqd6)4eor&~r?H=E;HMh+sRn+kfuCyN|3eL={%@wq BzNG*F From 74471ce23ec0cda683b70c8ae4a4f86e4a12990e Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 2 Jan 2023 11:07:57 +0100 Subject: [PATCH 13/30] chore(legacy): generate changelogs --- legacy/bootloader/.changelog.d/2423.fixed | 1 - legacy/bootloader/.changelog.d/2568.added | 1 - legacy/bootloader/CHANGELOG.md | 11 ++++++++++ legacy/firmware/.changelog.d/163.fixed | 1 - legacy/firmware/.changelog.d/1749.added | 1 - legacy/firmware/.changelog.d/2190.changed | 1 - legacy/firmware/.changelog.d/2289.changed | 1 - legacy/firmware/.changelog.d/2442.added | 1 - legacy/firmware/.changelog.d/2486.changed | 1 - legacy/firmware/.changelog.d/2487.changed | 1 - legacy/firmware/.changelog.d/2568.added | 1 - legacy/firmware/CHANGELOG.md | 25 +++++++++++++++++++++++ 12 files changed, 36 insertions(+), 10 deletions(-) delete mode 100644 legacy/bootloader/.changelog.d/2423.fixed delete mode 100644 legacy/bootloader/.changelog.d/2568.added delete mode 100644 legacy/firmware/.changelog.d/163.fixed delete mode 100644 legacy/firmware/.changelog.d/1749.added delete mode 100644 legacy/firmware/.changelog.d/2190.changed delete mode 100644 legacy/firmware/.changelog.d/2289.changed delete mode 100644 legacy/firmware/.changelog.d/2442.added delete mode 100644 legacy/firmware/.changelog.d/2486.changed delete mode 100644 legacy/firmware/.changelog.d/2487.changed delete mode 100644 legacy/firmware/.changelog.d/2568.added diff --git a/legacy/bootloader/.changelog.d/2423.fixed b/legacy/bootloader/.changelog.d/2423.fixed deleted file mode 100644 index 7e3c90472..000000000 --- a/legacy/bootloader/.changelog.d/2423.fixed +++ /dev/null @@ -1 +0,0 @@ -Better way to debug T1 combinations of debug/production combinations of bootloader and firmware diff --git a/legacy/bootloader/.changelog.d/2568.added b/legacy/bootloader/.changelog.d/2568.added deleted file mode 100644 index 77b0e992f..000000000 --- a/legacy/bootloader/.changelog.d/2568.added +++ /dev/null @@ -1 +0,0 @@ -T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging diff --git a/legacy/bootloader/CHANGELOG.md b/legacy/bootloader/CHANGELOG.md index ca0507070..6d09f401d 100644 --- a/legacy/bootloader/CHANGELOG.md +++ b/legacy/bootloader/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.12.0] [January 2023] + +### Added +- T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging [#2568] + +### Fixed +- Better way to debug T1 combinations of debug/production combinations of bootloader and firmware [#2423] + + ## 1.11.0 [May 2022] ### Added @@ -128,3 +137,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#1642]: https://github.com/trezor/trezor-firmware/pull/1642 [#1884]: https://github.com/trezor/trezor-firmware/pull/1884 [#2231]: https://github.com/trezor/trezor-firmware/pull/2231 +[#2423]: https://github.com/trezor/trezor-firmware/pull/2423 +[#2568]: https://github.com/trezor/trezor-firmware/pull/2568 diff --git a/legacy/firmware/.changelog.d/163.fixed b/legacy/firmware/.changelog.d/163.fixed deleted file mode 100644 index c21b51ff2..000000000 --- a/legacy/firmware/.changelog.d/163.fixed +++ /dev/null @@ -1 +0,0 @@ -Bootloader VTOR and FW handover fix diff --git a/legacy/firmware/.changelog.d/1749.added b/legacy/firmware/.changelog.d/1749.added deleted file mode 100644 index 26482972a..000000000 --- a/legacy/firmware/.changelog.d/1749.added +++ /dev/null @@ -1 +0,0 @@ -Support Ledger Live legacy derivation path "m/44'/coin_type'/0'/account" diff --git a/legacy/firmware/.changelog.d/2190.changed b/legacy/firmware/.changelog.d/2190.changed deleted file mode 100644 index 4872888c6..000000000 --- a/legacy/firmware/.changelog.d/2190.changed +++ /dev/null @@ -1 +0,0 @@ -Do not convert bech32 addresses to uppercase in QR code to increase compatibility diff --git a/legacy/firmware/.changelog.d/2289.changed b/legacy/firmware/.changelog.d/2289.changed deleted file mode 100644 index 245836460..000000000 --- a/legacy/firmware/.changelog.d/2289.changed +++ /dev/null @@ -1 +0,0 @@ -Do not allow access to SLIP25 paths. diff --git a/legacy/firmware/.changelog.d/2442.added b/legacy/firmware/.changelog.d/2442.added deleted file mode 100644 index c9b38f712..000000000 --- a/legacy/firmware/.changelog.d/2442.added +++ /dev/null @@ -1 +0,0 @@ -Show fee rate when replacing transaction diff --git a/legacy/firmware/.changelog.d/2486.changed b/legacy/firmware/.changelog.d/2486.changed deleted file mode 100644 index 41fe5c3fa..000000000 --- a/legacy/firmware/.changelog.d/2486.changed +++ /dev/null @@ -1 +0,0 @@ -Extend decimals of fee rate to 2 digits diff --git a/legacy/firmware/.changelog.d/2487.changed b/legacy/firmware/.changelog.d/2487.changed deleted file mode 100644 index 254933514..000000000 --- a/legacy/firmware/.changelog.d/2487.changed +++ /dev/null @@ -1 +0,0 @@ -Display only sat instead of sat BTC diff --git a/legacy/firmware/.changelog.d/2568.added b/legacy/firmware/.changelog.d/2568.added deleted file mode 100644 index 77b0e992f..000000000 --- a/legacy/firmware/.changelog.d/2568.added +++ /dev/null @@ -1 +0,0 @@ -T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging diff --git a/legacy/firmware/CHANGELOG.md b/legacy/firmware/CHANGELOG.md index 97247e77a..b5d5bc974 100644 --- a/legacy/firmware/CHANGELOG.md +++ b/legacy/firmware/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.12.0] (internal only - January 2023) + +### Added +- Support Ledger Live legacy derivation path "m/44'/coin_type'/0'/account" [#1749] +- Show fee rate when replacing transaction [#2442] +- T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging [#2568] + +### Changed +- Do not convert bech32 addresses to uppercase in QR code to increase compatibility [#2190] +- Do not allow access to SLIP25 paths. [#2289] +- Extend decimals of fee rate to 2 digits [#2486] +- Display only sat instead of sat BTC [#2487] + +### Fixed +- Bootloader VTOR and FW handover fix [#163] + + ## [1.11.2] (17th August 2022) ### Added @@ -508,6 +525,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Removed all current limits on size of signed transaction. [#131]: https://github.com/trezor/trezor-firmware/pull/131 +[#163]: https://github.com/trezor/trezor-firmware/pull/163 [#965]: https://github.com/trezor/trezor-firmware/pull/965 [#1018]: https://github.com/trezor/trezor-firmware/pull/1018 [#1030]: https://github.com/trezor/trezor-firmware/pull/1030 @@ -538,6 +556,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#1705]: https://github.com/trezor/trezor-firmware/pull/1705 [#1710]: https://github.com/trezor/trezor-firmware/pull/1710 [#1743]: https://github.com/trezor/trezor-firmware/pull/1743 +[#1749]: https://github.com/trezor/trezor-firmware/pull/1749 [#1755]: https://github.com/trezor/trezor-firmware/pull/1755 [#1765]: https://github.com/trezor/trezor-firmware/pull/1765 [#1767]: https://github.com/trezor/trezor-firmware/pull/1767 @@ -559,9 +578,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#2115]: https://github.com/trezor/trezor-firmware/pull/2115 [#2144]: https://github.com/trezor/trezor-firmware/pull/2144 [#2181]: https://github.com/trezor/trezor-firmware/pull/2181 +[#2190]: https://github.com/trezor/trezor-firmware/pull/2190 [#2239]: https://github.com/trezor/trezor-firmware/pull/2239 [#2249]: https://github.com/trezor/trezor-firmware/pull/2249 [#2261]: https://github.com/trezor/trezor-firmware/pull/2261 +[#2289]: https://github.com/trezor/trezor-firmware/pull/2289 [#2394]: https://github.com/trezor/trezor-firmware/pull/2394 [#2422]: https://github.com/trezor/trezor-firmware/pull/2422 [#2433]: https://github.com/trezor/trezor-firmware/pull/2433 +[#2442]: https://github.com/trezor/trezor-firmware/pull/2442 +[#2486]: https://github.com/trezor/trezor-firmware/pull/2486 +[#2487]: https://github.com/trezor/trezor-firmware/pull/2487 +[#2568]: https://github.com/trezor/trezor-firmware/pull/2568 From 20dace89ffec738ccc453606197d693e6d0daf0e Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 19 Dec 2022 16:26:33 +0100 Subject: [PATCH 14/30] feat(python/firmware): introduce a cleaner database of signing keys --- python/.changelog.d/2701.changed | 1 + python/src/trezorlib/firmware/consts.py | 49 ++----- python/src/trezorlib/firmware/models.py | 170 ++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 37 deletions(-) create mode 100644 python/.changelog.d/2701.changed create mode 100644 python/src/trezorlib/firmware/models.py diff --git a/python/.changelog.d/2701.changed b/python/.changelog.d/2701.changed new file mode 100644 index 000000000..3e2726d7d --- /dev/null +++ b/python/.changelog.d/2701.changed @@ -0,0 +1 @@ +More structured information about signing keys for different models. diff --git a/python/src/trezorlib/firmware/consts.py b/python/src/trezorlib/firmware/consts.py index fa63ad138..c03177d10 100644 --- a/python/src/trezorlib/firmware/consts.py +++ b/python/src/trezorlib/firmware/consts.py @@ -14,46 +14,21 @@ # You should have received a copy of the License along with this library. # If not, see . +from . import models + V1_SIGNATURE_SLOTS = 3 -V1_BOOTLOADER_KEYS = [ - bytes.fromhex(key) - for key in ( - "04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58", - "0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1", - "0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58", - "04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a", - "047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45", - ) -] -V2_BOARDLOADER_KEYS = [ - bytes.fromhex(key) - for key in ( - "0eb9856be9ba7e972c7f34eac1ed9b6fd0efd172ec00faf0c589759da4ddfba0", - "ac8ab40b32c98655798fd5da5e192be27a22306ea05c6d277cdff4a3f4125cd8", - "ce0fcd12543ef5936cf2804982136707863d17295faced72af171d6e6513ff06", - ) -] +ONEV2_CHUNK_SIZE = 1024 * 64 +V2_CHUNK_SIZE = 1024 * 128 -V2_BOARDLOADER_DEV_KEYS = [ - bytes.fromhex(key) - for key in ( - "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d", - "2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12", - "22fc297792f0b6ffc0bfcfdb7edb0c0aa14e025a365ec0e342e86e3829cb74b6", - ) -] -V2_BOOTLOADER_KEYS = [ - bytes.fromhex(key) - for key in ( - "c2c87a49c5a3460977fbb2ec9dfe60f06bd694db8244bd4981fe3b7a26307f3f", - "80d036b08739b846f4cb77593078deb25dc9487aedcf52e30b4fb7cd7024178a", - "b8307a71f552c60a4cbb317ff48b82cdbf6b6bb5f04c920fec7badf017883751", - ) -] +# === KEYS KEPT FOR COMPATIBILITY === +# use `trezorlib.firmware.models` directly -V2_SIGS_REQUIRED = 2 +V1_BOOTLOADER_KEYS = models.TREZOR_ONE_V1V2.firmware_keys +V2_BOARDLOADER_KEYS = models.TREZOR_T.boardloader_keys +V2_BOARDLOADER_DEV_KEYS = models.TREZOR_T_DEV.boardloader_keys +V2_BOOTLOADER_KEYS = models.TREZOR_T.bootloader_keys +V2_BOOTLOADER_DEV_KEYS = models.TREZOR_T_DEV.bootloader_keys -ONEV2_CHUNK_SIZE = 1024 * 64 -V2_CHUNK_SIZE = 1024 * 128 +V2_SIGS_REQUIRED = models.TREZOR_T.boardloader_sigs_needed diff --git a/python/src/trezorlib/firmware/models.py b/python/src/trezorlib/firmware/models.py new file mode 100644 index 000000000..6c2508c71 --- /dev/null +++ b/python/src/trezorlib/firmware/models.py @@ -0,0 +1,170 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2022 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import typing as t +from dataclasses import dataclass +from enum import Enum + + +class Model(Enum): + ONE = b"T1B1" + T = b"T2T1" + R = b"T2B1" + + +@dataclass +class ModelKeys: + """Model-specific keys.""" + + production: bool + boardloader_keys: t.Sequence[bytes] + boardloader_sigs_needed: int + bootloader_keys: t.Sequence[bytes] + bootloader_sigs_needed: int + firmware_keys: t.Sequence[bytes] + firmware_sigs_needed: int + + +TREZOR_ONE_V1V2 = ModelKeys( + production=True, + boardloader_keys=(), + boardloader_sigs_needed=-1, + bootloader_keys=(), + bootloader_sigs_needed=-1, + firmware_keys=[ + bytes.fromhex(key) + for key in ( + "04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58", + "0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1", + "0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58", + "04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a", + "047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45", + ) + ], + firmware_sigs_needed=3, +) + +TREZOR_ONE_V1V2_DEV = ModelKeys( + production=False, + boardloader_keys=(), + boardloader_sigs_needed=-1, + bootloader_keys=(), + bootloader_sigs_needed=-1, + firmware_keys=[ + bytes.fromhex(key) + for key in ( + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", + "03665f660a5052be7a95546a02179058d93d3e08a779734914594346075bb0afd4", + "0366635d999417b65566866c65630d977a7ae723fe5f6c4cd17fa00f088ba184c1", + "03f36c7d0fb615ada43d7188580f15ebda22d6f6b9b1a92bff16c6937799dcbc66", + ) + ], + firmware_sigs_needed=3, +) + +TREZOR_ONE_V3 = ModelKeys( + production=True, + boardloader_keys=(), + boardloader_sigs_needed=-1, + bootloader_keys=(), + bootloader_sigs_needed=-1, + firmware_keys=[ + bytes.fromhex(key) + for key in ( + "032300c1bb4539fcbfca2590bda3dd2093826f4ae437bddecc1a2e72520764ff7a", + "0233baeaebc94a2a3e8b11f39a7133dbf427be292fcbceb887d71ef51e85395a19", + "0357091fa254b55233d0bb4c48e106c91b92fd0788ebed9d3a916719f44c76c015", + ) + ], + firmware_sigs_needed=2, +) + +TREZOR_ONE_V3_DEV = ModelKeys( + production=False, + boardloader_keys=(), + boardloader_sigs_needed=-1, + bootloader_keys=(), + bootloader_sigs_needed=-1, + firmware_keys=[ + bytes.fromhex(key) + for key in ( + "037308e14077161c365dea0f5c80aa6c5dba34719e825bd23ae5f7e7d2988adb0f", + "039c1b2460e343712e982e0732e7ed17f60de4c933065b7170d99c6e7fe7cc7f4b", + "03152b37fdf126111274c894c348dcc975b57c115ee24ceb19b5190ac7f7b65173", + ) + ], + firmware_sigs_needed=2, +) + +TREZOR_T = ModelKeys( + production=True, + boardloader_keys=[ + bytes.fromhex(key) + for key in ( + "0eb9856be9ba7e972c7f34eac1ed9b6fd0efd172ec00faf0c589759da4ddfba0", + "ac8ab40b32c98655798fd5da5e192be27a22306ea05c6d277cdff4a3f4125cd8", + "ce0fcd12543ef5936cf2804982136707863d17295faced72af171d6e6513ff06", + ) + ], + boardloader_sigs_needed=2, + bootloader_keys=[ + bytes.fromhex(key) + for key in ( + "c2c87a49c5a3460977fbb2ec9dfe60f06bd694db8244bd4981fe3b7a26307f3f", + "80d036b08739b846f4cb77593078deb25dc9487aedcf52e30b4fb7cd7024178a", + "b8307a71f552c60a4cbb317ff48b82cdbf6b6bb5f04c920fec7badf017883751", + ) + ], + bootloader_sigs_needed=2, + firmware_keys=(), + firmware_sigs_needed=-1, +) + +TREZOR_T_DEV = ModelKeys( + production=False, + boardloader_keys=[ + bytes.fromhex(key) + for key in ( + "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d", + "2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12", + "22fc297792f0b6ffc0bfcfdb7edb0c0aa14e025a365ec0e342e86e3829cb74b6", + ) + ], + boardloader_sigs_needed=2, + bootloader_keys=[ + bytes.fromhex(key) + for key in ( + "d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48", + "6355691c178a8ff91007a7478afb955ef7352c63e7b25703984cf78b26e21a56", + "ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148", + ) + ], + bootloader_sigs_needed=2, + firmware_keys=(), + firmware_sigs_needed=-1, +) + + +MODEL_MAP = { + Model.ONE: TREZOR_ONE_V3, + Model.T: TREZOR_T, +} + +MODEL_MAP_DEV = { + Model.ONE: TREZOR_ONE_V3_DEV, + Model.T: TREZOR_T_DEV, +} From 66eedd164c8b2fc20994619704ddb9c5924d163c Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 19 Dec 2022 16:27:22 +0100 Subject: [PATCH 15/30] feat(python/firmware): introduce fw model field --- python/.changelog.d/2701.added.1 | 1 + python/src/trezorlib/firmware/core.py | 9 ++++++++- python/src/trezorlib/tools.py | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 python/.changelog.d/2701.added.1 diff --git a/python/.changelog.d/2701.added.1 b/python/.changelog.d/2701.added.1 new file mode 100644 index 000000000..ef30bc87b --- /dev/null +++ b/python/.changelog.d/2701.added.1 @@ -0,0 +1 @@ +Add support for model field in firmware image. diff --git a/python/src/trezorlib/firmware/core.py b/python/src/trezorlib/firmware/core.py index ed9a75a98..b8c853315 100644 --- a/python/src/trezorlib/firmware/core.py +++ b/python/src/trezorlib/firmware/core.py @@ -25,6 +25,7 @@ from construct_classes import Struct, subcon from .. import cosi from ..tools import EnumAdapter, TupleAdapter from . import consts, util +from .models import Model from .vendor import VendorHeader __all__ = [ @@ -47,6 +48,9 @@ class FirmwareHeader(Struct): code_length: int version: t.Tuple[int, int, int, int] fix_version: t.Tuple[int, int, int, int] + hw_model: Model + hw_revision: int + monotonic: int hashes: t.Sequence[bytes] v1_signatures: t.Sequence[bytes] @@ -69,7 +73,10 @@ class FirmwareHeader(Struct): ), "version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul), "fix_version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul), - "_reserved" / c.Padding(8), + "hw_model" / EnumAdapter(c.Bytes(4), Model), + "hw_revision" / c.Int8ul, + "monotonic" / c.Int8ul, + "_reserved" / c.Padding(2), "hashes" / c.Bytes(32)[16], "v1_signatures" / c.Bytes(64)[consts.V1_SIGNATURE_SLOTS], diff --git a/python/src/trezorlib/tools.py b/python/src/trezorlib/tools.py index 8162032b5..d33b4e50a 100644 --- a/python/src/trezorlib/tools.py +++ b/python/src/trezorlib/tools.py @@ -382,7 +382,9 @@ class EnumAdapter(construct.Adapter): super().__init__(subcon) def _encode(self, obj: Any, ctx: Any, path: Any): - return obj.value + if isinstance(obj, self.enum): + return obj.value + return obj def _decode(self, obj: Any, ctx: Any, path: Any): try: From b37afe01997c0a9ce6f7da34e78dce46e56ebe9e Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 19 Dec 2022 16:27:51 +0100 Subject: [PATCH 16/30] refactor(python/firmware): introduce legacy v3 signing detection related, change verify() method to accept dev_keys bool and select the appropriate keys, as opposed to caller needing to figure out the keys --- python/.changelog.d/2701.added | 1 + python/.changelog.d/2701.incompatible | 1 + python/src/trezorlib/firmware/__init__.py | 2 +- python/src/trezorlib/firmware/core.py | 8 +- python/src/trezorlib/firmware/legacy.py | 96 +++++++++++++++++++---- python/src/trezorlib/firmware/vendor.py | 14 +++- 6 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 python/.changelog.d/2701.added create mode 100644 python/.changelog.d/2701.incompatible diff --git a/python/.changelog.d/2701.added b/python/.changelog.d/2701.added new file mode 100644 index 000000000..dcead0943 --- /dev/null +++ b/python/.changelog.d/2701.added @@ -0,0 +1 @@ +Add support for v3-style Trezor One signatures. diff --git a/python/.changelog.d/2701.incompatible b/python/.changelog.d/2701.incompatible new file mode 100644 index 000000000..136848ec1 --- /dev/null +++ b/python/.changelog.d/2701.incompatible @@ -0,0 +1 @@ +Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. diff --git a/python/src/trezorlib/firmware/__init__.py b/python/src/trezorlib/firmware/__init__.py index 1979d843e..031b8132e 100644 --- a/python/src/trezorlib/firmware/__init__.py +++ b/python/src/trezorlib/firmware/__init__.py @@ -47,7 +47,7 @@ if t.TYPE_CHECKING: def parse(cls: t.Type[T], data: bytes) -> T: ... - def verify(self, public_keys: t.Sequence[bytes] = ()) -> None: + def verify(self, dev_keys: bool = False) -> None: ... def digest(self) -> bytes: diff --git a/python/src/trezorlib/firmware/core.py b/python/src/trezorlib/firmware/core.py index b8c853315..934dfb285 100644 --- a/python/src/trezorlib/firmware/core.py +++ b/python/src/trezorlib/firmware/core.py @@ -185,9 +185,11 @@ class VendorFirmware(Struct): def digest(self) -> bytes: return self.firmware.digest() - def verify(self, _public_keys: t.Sequence[bytes] = ()) -> None: - if _public_keys: - raise ValueError("Cannot supply custom keys for vendor firmware.") + def verify(self, dev_keys: bool = False) -> None: + if dev_keys: + raise ValueError( + "Cannot select dev keys for a vendor firmware; use development vendor header instead." + ) self.firmware.validate_code_hashes() diff --git a/python/src/trezorlib/firmware/legacy.py b/python/src/trezorlib/firmware/legacy.py index 6c7ed4f0e..1f6036d9c 100644 --- a/python/src/trezorlib/firmware/legacy.py +++ b/python/src/trezorlib/firmware/legacy.py @@ -22,7 +22,7 @@ import construct as c import ecdsa from construct_classes import Struct, subcon -from . import consts, util +from . import consts, models, util from .core import FirmwareImage __all__ = [ @@ -32,23 +32,32 @@ __all__ = [ ] +ZERO_SIG = b"\x00" * 64 + + def check_sig_v1( digest: bytes, key_indexes: t.Sequence[int], signatures: t.Sequence[bytes], + sigs_required: int, public_keys: t.Sequence[bytes], ) -> None: """Validate signatures of `digest` using the Trezor One V1 method.""" - distinct_indexes = set(i for i in key_indexes if i != 0) + distinct_indexes = set(i for i in key_indexes[:sigs_required] if i != 0) if not distinct_indexes: raise util.Unsigned - if len(distinct_indexes) < len(key_indexes): + if len(distinct_indexes) != sigs_required: raise util.InvalidSignatureError( - f"Not enough distinct signatures (found {len(distinct_indexes)}, need {len(key_indexes)})" + f"Not enough distinct signatures (found {len(distinct_indexes)}, need {sigs_required})" ) - for i in range(len(key_indexes)): + if any(k != 0 for k in key_indexes[sigs_required:]) or any( + sig != ZERO_SIG for sig in signatures[sigs_required:] + ): + raise util.InvalidSignatureError("Too many signatures") + + for i in range(sigs_required): key_idx = key_indexes[i] - 1 signature = signatures[i] @@ -56,14 +65,36 @@ def check_sig_v1( # unknown pubkey raise util.InvalidSignatureError(f"Unknown key in slot {i}") - pubkey = public_keys[key_idx][1:] - verify = ecdsa.VerifyingKey.from_string(pubkey, curve=ecdsa.curves.SECP256k1) + verify = ecdsa.VerifyingKey.from_string( + public_keys[key_idx], + curve=ecdsa.curves.SECP256k1, + hashfunc=hashlib.sha256, + ) try: verify.verify_digest(signature, digest) except ecdsa.BadSignatureError as e: raise util.InvalidSignatureError(f"Invalid signature in slot {i}") from e +def check_sig_signmessage( + digest: bytes, + key_indexes: t.Sequence[int], + signatures: t.Sequence[bytes], + sigs_required: int, + public_keys: t.Sequence[bytes], +) -> None: + """Validate signatures of `digest` using the Trezor One SignMessage method.""" + btc_digest = hashlib.sha256(b"\x18Bitcoin Signed Message:\n\x20" + digest).digest() + final_digest = hashlib.sha256(btc_digest).digest() + check_sig_v1( + final_digest, + key_indexes, + signatures, + sigs_required, + public_keys, + ) + + class LegacyV2Firmware(FirmwareImage): """Firmware image in the format used by Trezor One 1.8.0 and newer.""" @@ -73,17 +104,49 @@ class LegacyV2Firmware(FirmwareImage): padding_byte=b"\xff", ) - def verify( - self, public_keys: t.Sequence[bytes] = consts.V1_BOOTLOADER_KEYS - ) -> None: + V3_FIRST_VERSION = (1, 12, 0) + + def verify_v2(self, dev_keys: bool) -> None: + if not dev_keys: + public_keys = models.TREZOR_ONE_V1V2.firmware_keys + else: + public_keys = models.TREZOR_ONE_V1V2_DEV.firmware_keys + self.validate_code_hashes() check_sig_v1( self.digest(), self.header.v1_key_indexes, self.header.v1_signatures, + models.TREZOR_ONE_V1V2.firmware_sigs_needed, public_keys, ) + def verify_v3(self, dev_keys: bool) -> None: + if not dev_keys: + model_keys = models.TREZOR_ONE_V3 + else: + model_keys = models.TREZOR_ONE_V3_DEV + + self.validate_code_hashes() + check_sig_signmessage( + self.digest(), + self.header.v1_key_indexes, + self.header.v1_signatures, + model_keys.firmware_sigs_needed, + model_keys.firmware_keys, + ) + + def verify(self, dev_keys: bool = False) -> None: + if self.header.version >= self.V3_FIRST_VERSION: + try: + self.verify_v3(dev_keys) + except util.InvalidSignatureError: + pass + else: + return + + self.verify_v2(dev_keys) + def verify_unsigned(self) -> None: self.validate_code_hashes() if any(i != 0 for i in self.header.v1_key_indexes): @@ -126,15 +189,18 @@ class LegacyFirmware(Struct): def digest(self) -> bytes: return hashlib.sha256(self.code).digest() - def verify( - self, public_keys: t.Sequence[bytes] = consts.V1_BOOTLOADER_KEYS - ) -> None: + def verify(self, dev_keys: bool = False) -> None: + if not dev_keys: + model_keys = models.TREZOR_ONE_V1V2 + else: + model_keys = models.TREZOR_ONE_V1V2_DEV check_sig_v1( self.digest(), self.key_indexes, self.signatures, - public_keys, + model_keys.firmware_sigs_needed, + model_keys.firmware_keys, ) if self.embedded_v2: - self.embedded_v2.verify(consts.V1_BOOTLOADER_KEYS) + self.embedded_v2.verify() diff --git a/python/src/trezorlib/firmware/vendor.py b/python/src/trezorlib/firmware/vendor.py index 14696d3cc..154859e9e 100644 --- a/python/src/trezorlib/firmware/vendor.py +++ b/python/src/trezorlib/firmware/vendor.py @@ -24,7 +24,8 @@ from construct_classes import Struct, subcon from .. import cosi from ..toif import ToifStruct from ..tools import TupleAdapter -from . import consts, util +from . import util +from .models import TREZOR_T, TREZOR_T_DEV __all__ = [ "VendorTrust", @@ -125,14 +126,19 @@ class VendorHeader(Struct): h.update(b"\x00" * 32) return h.digest() - def verify(self, pubkeys: t.Sequence[bytes] = consts.V2_BOOTLOADER_KEYS) -> None: + def verify(self, dev_keys: bool = False) -> None: digest = self.digest() + if not dev_keys: + public_keys = TREZOR_T.bootloader_keys + else: + public_keys = TREZOR_T_DEV.bootloader_keys + # TODO: add model awareness try: cosi.verify( self.signature, digest, - consts.V2_SIGS_REQUIRED, - pubkeys, + TREZOR_T.bootloader_sigs_needed, + public_keys, self.sigmask, ) except Exception: From 629cfa54f85d54a0ec8578de1076857bbea00f98 Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 19 Dec 2022 16:55:34 +0100 Subject: [PATCH 17/30] refactor(python/firmware): improve headertool, recognize legacy v3 signatures --- .../trezorlib/_internal/firmware_headers.py | 120 ++++++++++++++---- 1 file changed, 98 insertions(+), 22 deletions(-) diff --git a/python/src/trezorlib/_internal/firmware_headers.py b/python/src/trezorlib/_internal/firmware_headers.py index 5010b2766..f3440dcc0 100644 --- a/python/src/trezorlib/_internal/firmware_headers.py +++ b/python/src/trezorlib/_internal/firmware_headers.py @@ -25,6 +25,7 @@ from construct_classes import Struct from typing_extensions import Protocol, Self, runtime_checkable from .. import cosi, firmware +from ..firmware import models as fw_models SYM_OK = click.style("\u2714", fg="green") SYM_FAIL = click.style("\u274c", fg="red") @@ -59,6 +60,12 @@ def _check_signature_any(fw: "SignableImageProto", is_devel: bool) -> Status: try: fw.verify() return Status.VALID if not is_devel else Status.DEVEL + except Exception: + pass + + try: + fw.verify(dev_keys=True) + return Status.DEVEL except Exception: return Status.INVALID @@ -184,24 +191,42 @@ class SignableImageProto(Protocol): @classmethod def parse(cls, data: bytes) -> Self: + """Parse binary data into an image of this type.""" ... def digest(self) -> bytes: + """Calculate digest that will be signed / verified.""" ... - def verify(self) -> None: + def verify(self, dev_keys: bool = False) -> None: + """Verify signature of the image. + + If dev_keys is True, verify using development keys. If selected, a production + image will fail verification. + """ ... def build(self) -> bytes: + """Reconstruct binary representation of the image.""" ... def format(self, verbose: bool = False) -> str: + """Generate printable information about the image.""" ... def signature_present(self) -> bool: + """Check if the image has a signature.""" ... - def public_keys(self) -> t.Sequence[bytes]: + def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: + """Return public keys that should be used to sign the image. + + This does _not_ return the keys with which the image is actually signed. + In particular, `image.public_keys()` will return the production + keys even if the image is signed with development keys. + + If dev_keys is True, return development keys. + """ ... @@ -221,6 +246,22 @@ class LegacySignedImage(SignableImageProto, Protocol): def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None: ... + def public_keys( + self, dev_keys: bool = False, signature_version: int = 3 + ) -> t.Sequence[bytes]: + """Return public keys that should be used to sign the image. + + This does _not_ return the keys with which the image is actually signed. + In particular, `image.public_keys()` will return the production + keys even if the image is signed with development keys. + + If dev_keys is True, return development keys. + + Specifying signature_version allows to return keys for a different signature + scheme version. The default is the newest version 3. + """ + ... + class CosiSignatureHeaderProto(Protocol): signature: bytes @@ -241,7 +282,7 @@ class CosiSignedMixin: class VendorHeader(firmware.VendorHeader, CosiSignedMixin): - NAME = "vendorheader" + NAME: t.ClassVar[str] = "vendorheader" DEV_KEYS = _make_dev_keys(b"\x44", b"\x45") SUBCON = c.Struct(*firmware.VendorHeader.SUBCON.subcons, c.Terminated) @@ -276,12 +317,15 @@ class VendorHeader(firmware.VendorHeader, CosiSignedMixin): def format(self, verbose: bool = False) -> str: return self._format(terse=False) - def public_keys(self) -> t.Sequence[bytes]: - return firmware.V2_BOOTLOADER_KEYS + def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: + if not dev_keys: + return fw_models.TREZOR_T.bootloader_keys + else: + return fw_models.TREZOR_T_DEV.bootloader_keys class VendorFirmware(firmware.VendorFirmware, CosiSignedMixin): - NAME = "firmware" + NAME: t.ClassVar[str] = "firmware" DEV_KEYS = _make_dev_keys(b"\x47", b"\x48") def get_header(self) -> CosiSignatureHeaderProto: @@ -305,14 +349,32 @@ class VendorFirmware(firmware.VendorFirmware, CosiSignedMixin): ) ) - def public_keys(self) -> t.Sequence[bytes]: + def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: + """Return public keys that should be used to sign the image. + + In vendor firmware, the public keys are stored in the vendor header. + There is no choice of development keys. If that is required, you need to create + an image with a development vendor header. + """ return self.vendor_header.pubkeys class BootloaderImage(firmware.FirmwareImage, CosiSignedMixin): - NAME = "bootloader" + NAME: t.ClassVar[str] = "bootloader" DEV_KEYS = _make_dev_keys(b"\x41", b"\x42") + def get_model(self) -> fw_models.Model: + if isinstance(self.header.hw_model, fw_models.Model): + return self.header.hw_model + return fw_models.Model.T + + def get_model_keys(self, dev_keys: bool) -> fw_models.ModelKeys: + model = self.get_model() + if dev_keys: + return fw_models.MODEL_MAP_DEV[model] + else: + return fw_models.MODEL_MAP[model] + def get_header(self) -> CosiSignatureHeaderProto: return self.header @@ -324,26 +386,26 @@ class BootloaderImage(firmware.FirmwareImage, CosiSignedMixin): _check_signature_any(self, False), ) - def verify(self) -> None: + def verify(self, dev_keys: bool = False) -> None: self.validate_code_hashes() + public_keys = self.public_keys(dev_keys) try: cosi.verify( self.header.signature, self.digest(), - firmware.V2_SIGS_REQUIRED, - firmware.V2_BOARDLOADER_KEYS, + self.get_model_keys(dev_keys).boardloader_sigs_needed, + public_keys, self.header.sigmask, ) except Exception: raise firmware.InvalidSignatureError("Invalid bootloader signature") - def public_keys(self) -> t.Sequence[bytes]: - return firmware.V2_BOARDLOADER_KEYS + def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: + return self.get_model_keys(dev_keys).boardloader_keys class LegacyFirmware(firmware.LegacyFirmware): - NAME = "legacy_firmware_v1" - BIP32_INDEX = None + NAME: t.ClassVar[str] = "legacy_firmware_v1" def signature_present(self) -> bool: return any(i != 0 for i in self.key_indexes) or any( @@ -353,7 +415,7 @@ class LegacyFirmware(firmware.LegacyFirmware): def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None: if not 0 <= slot < firmware.V1_SIGNATURE_SLOTS: raise ValueError("Invalid slot number") - if not 0 < key_index <= len(firmware.V1_BOOTLOADER_KEYS): + if not 0 < key_index <= len(fw_models.TREZOR_ONE_V1V2.firmware_keys): raise ValueError("Invalid key index") self.key_indexes[slot] = key_index self.signatures[slot] = signature @@ -371,16 +433,20 @@ class LegacyFirmware(firmware.LegacyFirmware): return _format_container(contents) + embedded_content - def public_keys(self) -> t.Sequence[bytes]: - return firmware.V1_BOOTLOADER_KEYS + def public_keys( + self, dev_keys: bool = False, signature_version: int = 2 + ) -> t.Sequence[bytes]: + if dev_keys: + return fw_models.TREZOR_ONE_V1V2_DEV.firmware_keys + else: + return fw_models.TREZOR_ONE_V1V2.firmware_keys def slots(self) -> t.Iterable[int]: return self.key_indexes class LegacyV2Firmware(firmware.LegacyV2Firmware): - NAME = "legacy_firmware_v2" - BIP32_INDEX = 5 + NAME: t.ClassVar[str] = "legacy_firmware_v2" def signature_present(self) -> bool: return any(i != 0 for i in self.header.v1_key_indexes) or any( @@ -407,8 +473,18 @@ class LegacyV2Firmware(firmware.LegacyV2Firmware): _check_signature_any(self, False), ) - def public_keys(self) -> t.Sequence[bytes]: - return firmware.V1_BOOTLOADER_KEYS + def public_keys( + self, dev_keys: bool = False, signature_version: int = 3 + ) -> t.Sequence[bytes]: + keymap: t.Dict[t.Tuple[int, bool], fw_models.ModelKeys] = { + (3, False): fw_models.TREZOR_ONE_V3, + (3, True): fw_models.TREZOR_ONE_V3_DEV, + (2, False): fw_models.TREZOR_ONE_V1V2, + (2, True): fw_models.TREZOR_ONE_V1V2_DEV, + } + if not (signature_version, dev_keys) in keymap: + raise ValueError("Unsupported signature version") + return keymap[signature_version, dev_keys].firmware_keys def slots(self) -> t.Iterable[int]: return self.header.v1_key_indexes From b96c3e22e385dacac0322060940938e1f5ab4723 Mon Sep 17 00:00:00 2001 From: matejcik Date: Wed, 28 Dec 2022 16:21:23 +0100 Subject: [PATCH 18/30] docs(python): changelog for 0.13.5 release --- python/.changelog.d/2701.added | 1 - python/.changelog.d/2701.added.1 | 1 - python/.changelog.d/2701.changed | 1 - python/.changelog.d/2701.incompatible | 1 - python/CHANGELOG.md | 15 +++++++++++++++ 5 files changed, 15 insertions(+), 4 deletions(-) delete mode 100644 python/.changelog.d/2701.added delete mode 100644 python/.changelog.d/2701.added.1 delete mode 100644 python/.changelog.d/2701.changed delete mode 100644 python/.changelog.d/2701.incompatible diff --git a/python/.changelog.d/2701.added b/python/.changelog.d/2701.added deleted file mode 100644 index dcead0943..000000000 --- a/python/.changelog.d/2701.added +++ /dev/null @@ -1 +0,0 @@ -Add support for v3-style Trezor One signatures. diff --git a/python/.changelog.d/2701.added.1 b/python/.changelog.d/2701.added.1 deleted file mode 100644 index ef30bc87b..000000000 --- a/python/.changelog.d/2701.added.1 +++ /dev/null @@ -1 +0,0 @@ -Add support for model field in firmware image. diff --git a/python/.changelog.d/2701.changed b/python/.changelog.d/2701.changed deleted file mode 100644 index 3e2726d7d..000000000 --- a/python/.changelog.d/2701.changed +++ /dev/null @@ -1 +0,0 @@ -More structured information about signing keys for different models. diff --git a/python/.changelog.d/2701.incompatible b/python/.changelog.d/2701.incompatible deleted file mode 100644 index 136848ec1..000000000 --- a/python/.changelog.d/2701.incompatible +++ /dev/null @@ -1 +0,0 @@ -Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index c349114e0..e5e2e8f72 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.13.5] (2022-12-28) +[0.13.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.4...python/v0.13.5 + +### Added +- Add support for model field in firmware image. [#2701] +- Add support for v3-style Trezor One signatures. [#2701] + +### Changed +- More structured information about signing keys for different models. [#2701] + +### Incompatible changes +- Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. [#2701] + + ## [0.13.4] (2022-11-04) [0.13.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.3...python/v0.13.4 @@ -702,3 +716,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#2547]: https://github.com/trezor/trezor-firmware/pull/2547 [#2561]: https://github.com/trezor/trezor-firmware/pull/2561 [#2576]: https://github.com/trezor/trezor-firmware/pull/2576 +[#2701]: https://github.com/trezor/trezor-firmware/pull/2701 From a97dfd3a4f4a709a08e125946f6e1c96a0cc7ec0 Mon Sep 17 00:00:00 2001 From: matejcik Date: Wed, 28 Dec 2022 16:27:09 +0100 Subject: [PATCH 19/30] chore(python): bump version after release --- python/src/trezorlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/trezorlib/__init__.py b/python/src/trezorlib/__init__.py index 4b18cecd2..d0347dc84 100644 --- a/python/src/trezorlib/__init__.py +++ b/python/src/trezorlib/__init__.py @@ -14,4 +14,4 @@ # You should have received a copy of the License along with this library. # If not, see . -__version__ = "0.13.5" +__version__ = "0.13.6" From 0c3b0371dd11406009b519ecce92b9d9aab4e561 Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 2 Jan 2023 11:10:46 +0100 Subject: [PATCH 20/30] chore(legacy): bump versions after release --- legacy/bootloader/version.h | 2 +- legacy/firmware/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/legacy/bootloader/version.h b/legacy/bootloader/version.h index ebb892251..f076e4c48 100644 --- a/legacy/bootloader/version.h +++ b/legacy/bootloader/version.h @@ -1,3 +1,3 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 12 -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h index 234cdbd7a..2e77d533c 100644 --- a/legacy/firmware/version.h +++ b/legacy/firmware/version.h @@ -1,6 +1,6 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 12 -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 #define FIX_VERSION_MAJOR 1 #define FIX_VERSION_MINOR 12 From daf566a2603e1c04959d7169646576b32ef46e40 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Wed, 4 Jan 2023 17:26:57 +0100 Subject: [PATCH 21/30] fix(legacy): fix v2 signature validation --- legacy/bootloader/bootloader.c | 6 ------ legacy/bootloader/usb.c | 10 +++++++++- legacy/fw_signatures.c | 17 ++++++++++------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/legacy/bootloader/bootloader.c b/legacy/bootloader/bootloader.c index 66c1d46bb..af78230e9 100644 --- a/legacy/bootloader/bootloader.c +++ b/legacy/bootloader/bootloader.c @@ -64,7 +64,6 @@ void show_unplug(const char *line1, const char *line2) { "You may now", "unplug your Trezor.", NULL); } -#if !BOOTLOADER_QA static void show_unofficial_warning(const uint8_t *hash) { // On production bootloader, show warning and wait for user // to accept or reject it @@ -94,7 +93,6 @@ static void show_unofficial_warning(const uint8_t *hash) { delay(100000000); #endif } -#endif static void __attribute__((noreturn)) load_app(int signed_firmware) { // zero out SRAM @@ -161,11 +159,7 @@ int main(void) { uint8_t fingerprint[32] = {0}; int signed_firmware = signatures_match(hdr, fingerprint); if (SIG_OK != signed_firmware) { -#if BOOTLOADER_QA - show_halt("Unsigned firmware", "Won't run on QA device"); -#else show_unofficial_warning(fingerprint); -#endif } #if !PRODUCTION && !BOOTLOADER_QA && !DEBUG_T1_SIGNATURES // try to avoid bricking board SWD debug by accident diff --git a/legacy/bootloader/usb.c b/legacy/bootloader/usb.c index 7412f34ed..37b5c066e 100644 --- a/legacy/bootloader/usb.c +++ b/legacy/bootloader/usb.c @@ -412,12 +412,20 @@ static void rx_callback(usbd_device *dev, uint8_t ep) { if (flash_state == STATE_CHECK) { // use the firmware header from RAM - const image_header *hdr = (const image_header *)FW_HEADER; + image_header *hdr = (image_header *)FW_HEADER; bool hash_check_ok; // show fingerprint of unsigned firmware // allow only v3 signmessage/verifymessage signatures if (SIG_OK != signatures_ok(hdr, NULL, sectrue)) { + // clear invalid signatures + hdr->sigindex1 = 0; + hdr->sigindex2 = 0; + hdr->sigindex3 = 0; + memset(hdr->sig1, 0, sizeof(hdr->sig1)); + memset(hdr->sig2, 0, sizeof(hdr->sig2)); + memset(hdr->sig3, 0, sizeof(hdr->sig3)); + if (msg_id != 0x001B) { // ButtonAck message (id 27) return; } diff --git a/legacy/fw_signatures.c b/legacy/fw_signatures.c index 24b0046c2..92e0f85cc 100644 --- a/legacy/fw_signatures.c +++ b/legacy/fw_signatures.c @@ -191,9 +191,10 @@ int signatures_ok(const image_header *hdr, uint8_t store_fingerprint[32], return SIG_FAIL; // invalid index if (hdr->sigindex2 < 1 || hdr->sigindex2 > pubkeys) return SIG_FAIL; // invalid index - if (use_verifymessage != sectrue && - (hdr->sigindex3 < 1 || hdr->sigindex3 > pubkeys)) { - return SIG_FAIL; // invalid index + if (use_verifymessage != sectrue) { + if (hdr->sigindex3 < 1 || hdr->sigindex3 > pubkeys) { + return SIG_FAIL; // invalid index + } } else if (hdr->sigindex3 != 0) { return SIG_FAIL; } @@ -210,10 +211,12 @@ int signatures_ok(const image_header *hdr, uint8_t store_fingerprint[32], hdr->sig2, hash)) { // failure return SIG_FAIL; } - if (use_verifymessage != sectrue && - (0 != ecdsa_verify_digest(&secp256k1, pubkey_ptr[hdr->sigindex3 - 1], - hdr->sig3, hash))) { // failure - return SIG_FAIL; + if (use_verifymessage != sectrue) { + if (0 != ecdsa_verify_digest(&secp256k1, pubkey_ptr[hdr->sigindex3 - 1], + hdr->sig3, hash)) // failure + { + return SIG_FAIL; + } } else { for (unsigned int i = 0; i < sizeof(hdr->sig3); i++) { if (hdr->sig3[i] != 0) { From 752bbebceef3e26a9781583132dda4d750907a49 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Thu, 26 Jan 2023 11:33:29 +0100 Subject: [PATCH 22/30] chore(legacy): added signing script for v3 debug keys [no changelog] --- .../sign_firmware_v3_signature.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 legacy/debug_signing/sign_firmware_v3_signature.py diff --git a/legacy/debug_signing/sign_firmware_v3_signature.py b/legacy/debug_signing/sign_firmware_v3_signature.py new file mode 100644 index 000000000..e6a0ce4a6 --- /dev/null +++ b/legacy/debug_signing/sign_firmware_v3_signature.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import typing as t +from hashlib import sha256 +from pathlib import Path + +import click +import ecdsa + +from trezorlib.firmware.legacy import LegacyV2Firmware +from trezorlib.firmware.models import TREZOR_ONE_V3_DEV + +SECRET_KEYS = [ + ecdsa.SigningKey.from_string(bytes.fromhex(sk), curve=ecdsa.SECP256k1) + for sk in ( + "ca8de06e1e93d101136fa6fbc41432c52b6530299dfe32808030ee8e679702f1", + "dde47dd393f7d76f9b522bfa9760bc4543d2c3654491393774f54e066461fccb", + "14ba98c5c5cb8f1b214c661f4046ec2288c34fe2e73e02f149ec3dd8dad07ae2", + ) +] + +PUBLIC_KEYS: list[ecdsa.VerifyingKey] = [sk.get_verifying_key() for sk in SECRET_KEYS] + +# Should be these public keys +assert [ + pk.to_string("compressed") for pk in PUBLIC_KEYS +] == TREZOR_ONE_V3_DEV.firmware_keys + + +def signmessage(digest: bytes, key: ecdsa.SigningKey) -> bytes: + """Sign via SignMessage""" + btc_digest = b"\x18Bitcoin Signed Message:\n\x20" + digest + final_digest = sha256(sha256(btc_digest).digest()).digest() + return key.sign_digest_deterministic(final_digest, hashfunc=sha256) + + +@click.command() +@click.argument("firmware", type=click.File("rb")) +@click.option("-i", "--index", "indices", type=int, multiple=True) +def cli(firmware: click.utils.LazyFile, indices: t.Sequence[int]) -> None: + fw = LegacyV2Firmware.parse(firmware.read()) + if not indices: + indices = [1, 2] + + if len(indices) > 3: + raise click.ClickException("Too many indices") + + digest = fw.digest() + for i, idx in enumerate(indices): + sk = SECRET_KEYS[idx - 1] + sig = signmessage(digest, sk) + + fw.header.v1_key_indexes[i] = idx + fw.header.v1_signatures[i] = sig + + new_fw = Path(firmware.name).with_suffix(".signed.bin") + new_fw.write_bytes(fw.build()) + click.echo(f"Wrote signed firmware to {new_fw}") + + +if __name__ == "__main__": + cli() From 37c13ed75efa051da61fdb2c97811f5d02cd3095 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Thu, 26 Jan 2023 11:43:57 +0100 Subject: [PATCH 23/30] chore(legacy): bundle bootloader 1.12.1 into firmware --- legacy/firmware/bl_check.c | 12 ++++++++++++ legacy/firmware/bl_check.txt | 1 + legacy/firmware/bl_check_qa.txt | 1 + legacy/firmware/bootloader.dat | Bin 32768 -> 32768 bytes legacy/firmware/bootloader_qa.dat | Bin 32768 -> 32768 bytes legacy/intermediate_fw/version.h | 2 +- 6 files changed, 15 insertions(+), 1 deletion(-) diff --git a/legacy/firmware/bl_check.c b/legacy/firmware/bl_check.c index 996d1fbf9..47a034bee 100644 --- a/legacy/firmware/bl_check.c +++ b/legacy/firmware/bl_check.c @@ -96,6 +96,12 @@ static int known_bootloader(int r, const uint8_t *hash) { "\xb4\x54\x18\x19\x65\x31\xb9\xf2\x97\x4a\x68\xed\xe8\xdb\x2e\xa1", 32)) return 1; // 1.12.0 shipped with fw 1.12.0 + if (0 == + memcmp(hash, + "\xb4\xe5\x92\x44\x18\x5c\xe1\xcd\x6c\x8f\x59\x03\x10\x37\x02\x18" + "\x50\x9c\x39\x32\x26\xf0\x07\x4b\x8c\xf6\xad\xed\xb3\xcd\x4d\x55", + 32)) + return 1; // 1.12.1 shipped with fw 1.12.1 // END AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt) return 0; } @@ -220,6 +226,12 @@ static int known_bootloader(int r, const uint8_t *hash) { "\x61\xb3\xee\xfb\x8a\x0d\xda\xa3\x4d\x7e\x45\xb6\x3b\x3d\x56\x64", 32)) return 1; // 1.12.0 shipped with fw 1.12.0 + if (0 == + memcmp(hash, + "\x94\xf1\xc9\x0d\xb2\x8d\xb1\xf8\xce\x5d\xca\x96\x69\x76\x34\x36" + "\x58\xf5\xda\xde\xe8\x38\x34\x98\x7c\x8b\x04\x9c\x49\xd1\xed\xd0", + 32)) + return 1; // 1.12.1 shipped with fw 1.12.1 // END AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt) return 0; } diff --git a/legacy/firmware/bl_check.txt b/legacy/firmware/bl_check.txt index 1d9ba4afe..a2f8376eb 100644 --- a/legacy/firmware/bl_check.txt +++ b/legacy/firmware/bl_check.txt @@ -1 +1,2 @@ e9ec8fa2fefad2b3b6b7c4ab76691a3361b3eefb8a0ddaa34d7e45b63b3d5664 1.12.0 shipped with fw 1.12.0 +94f1c90db28db1f8ce5dca966976343658f5dadee83834987c8b049c49d1edd0 1.12.1 shipped with fw 1.12.1 diff --git a/legacy/firmware/bl_check_qa.txt b/legacy/firmware/bl_check_qa.txt index 1850bad14..994e3def5 100644 --- a/legacy/firmware/bl_check_qa.txt +++ b/legacy/firmware/bl_check_qa.txt @@ -1 +1,2 @@ 21b949f4f5fd9f3a7d6343d1076f960fb45418196531b9f2974a68ede8db2ea1 1.12.0 shipped with fw 1.12.0 +b4e59244185ce1cd6c8f590310370218509c393226f0074b8cf6adedb3cd4d55 1.12.1 shipped with fw 1.12.1 diff --git a/legacy/firmware/bootloader.dat b/legacy/firmware/bootloader.dat index 48b798065f6d36f9f15feafa17affefd7561a93e..cd44dc7460a3464c6c22113b6b239bf9c24d2677 100644 GIT binary patch delta 4430 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 delta 4392 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 IP<`cp1BN0Oo&W#< diff --git a/legacy/firmware/bootloader_qa.dat b/legacy/firmware/bootloader_qa.dat index f882d985ac03d333d10b3fb1ff5a7d244881e67d..e7bc68adeb77b3ee28acdc86b101ecc385b620a0 100644 GIT binary patch delta 6978 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 delta 6513 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 Sn}AZic~ diff --git a/legacy/intermediate_fw/version.h b/legacy/intermediate_fw/version.h index 262093176..7bf83f3c7 100644 --- a/legacy/intermediate_fw/version.h +++ b/legacy/intermediate_fw/version.h @@ -1,7 +1,7 @@ // Matches the bootloader version included in this firmware #define VERSION_MAJOR 1 #define VERSION_MINOR 12 -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 #define FIX_VERSION_MAJOR 1 #define FIX_VERSION_MINOR 12 From af1f73a3733e0ed8f56ed16ef23cfa8aa9277ee6 Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 6 Feb 2023 10:10:23 +0100 Subject: [PATCH 24/30] docs(legacy): update changelogs --- legacy/bootloader/CHANGELOG.md | 8 +++++++- tools/towncrier.template.md | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/legacy/bootloader/CHANGELOG.md b/legacy/bootloader/CHANGELOG.md index 6d09f401d..8067ffb86 100644 --- a/legacy/bootloader/CHANGELOG.md +++ b/legacy/bootloader/CHANGELOG.md @@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [1.12.0] [January 2023] +## 1.12.1 [February 2023] + +### Fixed +- Correctly distrust v2-signed firmwares. + + +## 1.12.0 [internal only - January 2023] ### Added - T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging [#2568] diff --git a/tools/towncrier.template.md b/tools/towncrier.template.md index 2a5c58a02..834012add 100644 --- a/tools/towncrier.template.md +++ b/tools/towncrier.template.md @@ -1,5 +1,5 @@ -## [{{ versiondata.version }}] ({{versiondata.date}}) +## {{ versiondata.version }} [{{versiondata.date}}] {% for section, _ in sections.items() %} {% if section %}{{section}}{% endif -%} {% if sections[section] %} From 03c64f8faad24c27203ee298689b3d8707158142 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Thu, 26 Jan 2023 11:46:22 +0100 Subject: [PATCH 25/30] chore(legacy): bump versions after release --- legacy/bootloader/version.h | 2 +- legacy/firmware/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/legacy/bootloader/version.h b/legacy/bootloader/version.h index f076e4c48..3d7066741 100644 --- a/legacy/bootloader/version.h +++ b/legacy/bootloader/version.h @@ -1,3 +1,3 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 12 -#define VERSION_PATCH 1 +#define VERSION_PATCH 2 diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h index 2e77d533c..77eccd0f5 100644 --- a/legacy/firmware/version.h +++ b/legacy/firmware/version.h @@ -1,6 +1,6 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 12 -#define VERSION_PATCH 1 +#define VERSION_PATCH 2 #define FIX_VERSION_MAJOR 1 #define FIX_VERSION_MINOR 12 From 53cac7381f92474c08e89c6b37f529b46791f9a3 Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 6 Feb 2023 10:52:14 +0100 Subject: [PATCH 26/30] build: improve option parsing for build-docker.sh --- build-docker.sh | 82 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/build-docker.sh b/build-docker.sh index 18444ebe7..52ae60b62 100755 --- a/build-docker.sh +++ b/build-docker.sh @@ -40,27 +40,81 @@ ALPINE_TARBALL=${ALPINE_FILE:-alpine-minirootfs-$ALPINE_VERSION-$ALPINE_ARCH.tar NIX_VERSION=${NIX_VERSION:-2.4} CONTAINER_FS_URL=${CONTAINER_FS_URL:-"$ALPINE_CDN/v$ALPINE_RELEASE/releases/$ALPINE_ARCH/$ALPINE_TARBALL"} -VARIANTS_core=(0 1) -VARIANTS_legacy=(0 1) +function help_and_die() { + echo "Usage: $0 [options] tag" + echo "Options:" + echo " --skip-bitcoinonly" + echo " --skip-normal" + echo " --skip-core" + echo " --skip-legacy" + echo " --repository path/to/repo" + echo " --help" + echo + echo "Set PRODUCTION=0 to run non-production builds." + exit 0 +} + +OPT_BUILD_CORE=1 +OPT_BUILD_LEGACY=1 +OPT_BUILD_NORMAL=1 +OPT_BUILD_BITCOINONLY=1 + +REPOSITORY="/local" -if [ "$1" == "--skip-bitcoinonly" ]; then - VARIANTS_core=(0) - VARIANTS_legacy=(0) - shift +while true; do + case "$1" in + -h|--help) + help_and_die + ;; + --skip-bitcoinonly) + OPT_BUILD_BITCOINONLY=0 + shift + ;; + --skip-normal) + OPT_BUILD_NORMAL=0 + shift + ;; + --skip-core) + OPT_BUILD_CORE=0 + shift + ;; + --skip-legacy) + OPT_BUILD_LEGACY=0 + shift + ;; + --repository) + REPOSITORY="$2" + shift 2 + ;; + *) + break + ;; + esac +done + +if [ -z "$1" ]; then + help_and_die fi -if [ "$1" == "--skip-core" ]; then - VARIANTS_core=() - shift +variants=() +if [ "$OPT_BUILD_NORMAL" -eq 1 ]; then + variants+=(0) +fi +if [ "$OPT_BUILD_BITCOINONLY" -eq 1 ]; then + variants+=(1) fi -if [ "$1" == "--skip-legacy" ]; then - VARIANTS_legacy=() - shift +VARIANTS_core=() +VARIANTS_legacy=() + +if [ "$OPT_BUILD_CORE" -eq 1 ]; then + VARIANTS_core=("${variants[@]}") +fi +if [ "$OPT_BUILD_LEGACY" -eq 1 ]; then + VARIANTS_legacy=("${variants[@]}") fi -TAG=${1:-master} -REPOSITORY=${2:-/local} +TAG="$1" PRODUCTION=${PRODUCTION:-1} From b102d8879d76993a82fb28b06294aec98a4a9ff1 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 28 Feb 2023 15:44:03 +0100 Subject: [PATCH 27/30] docs(legacy/firmware): changelog for 1.12.1 --- legacy/firmware/.changelog.d/1453.fixed | 1 - legacy/firmware/.changelog.d/2373.fixed | 1 - legacy/firmware/.changelog.d/2682.added | 1 - legacy/firmware/.changelog.d/2718.added | 1 - legacy/firmware/.changelog.d/2718.added.1 | 1 - legacy/firmware/.changelog.d/2718.added.2 | 1 - legacy/firmware/.changelog.d/2718.added.3 | 1 - legacy/firmware/.changelog.d/2718.added.4 | 1 - legacy/firmware/.changelog.d/2743.changed | 1 - legacy/firmware/.changelog.d/noissue.security | 1 - legacy/firmware/CHANGELOG.md | 37 ++++++++++++++----- 11 files changed, 27 insertions(+), 20 deletions(-) delete mode 100644 legacy/firmware/.changelog.d/1453.fixed delete mode 100644 legacy/firmware/.changelog.d/2373.fixed delete mode 100644 legacy/firmware/.changelog.d/2682.added delete mode 100644 legacy/firmware/.changelog.d/2718.added delete mode 100644 legacy/firmware/.changelog.d/2718.added.1 delete mode 100644 legacy/firmware/.changelog.d/2718.added.2 delete mode 100644 legacy/firmware/.changelog.d/2718.added.3 delete mode 100644 legacy/firmware/.changelog.d/2718.added.4 delete mode 100644 legacy/firmware/.changelog.d/2743.changed delete mode 100644 legacy/firmware/.changelog.d/noissue.security diff --git a/legacy/firmware/.changelog.d/1453.fixed b/legacy/firmware/.changelog.d/1453.fixed deleted file mode 100644 index 1672b03f0..000000000 --- a/legacy/firmware/.changelog.d/1453.fixed +++ /dev/null @@ -1 +0,0 @@ -Show full Stellar address and QR code. diff --git a/legacy/firmware/.changelog.d/2373.fixed b/legacy/firmware/.changelog.d/2373.fixed deleted file mode 100644 index 631922d94..000000000 --- a/legacy/firmware/.changelog.d/2373.fixed +++ /dev/null @@ -1 +0,0 @@ -Wrap long Ethereum fee to next line if it does not fit. diff --git a/legacy/firmware/.changelog.d/2682.added b/legacy/firmware/.changelog.d/2682.added deleted file mode 100644 index 265e0d1f3..000000000 --- a/legacy/firmware/.changelog.d/2682.added +++ /dev/null @@ -1 +0,0 @@ -Allow proposed Casa m/45' multisig paths for Bitcoin and Ethereum. diff --git a/legacy/firmware/.changelog.d/2718.added b/legacy/firmware/.changelog.d/2718.added deleted file mode 100644 index ba5e0de52..000000000 --- a/legacy/firmware/.changelog.d/2718.added +++ /dev/null @@ -1 +0,0 @@ -Support native SegWit external inputs with non-ownership proof. diff --git a/legacy/firmware/.changelog.d/2718.added.1 b/legacy/firmware/.changelog.d/2718.added.1 deleted file mode 100644 index 2cbac75c0..000000000 --- a/legacy/firmware/.changelog.d/2718.added.1 +++ /dev/null @@ -1 +0,0 @@ -Implement SLIP-0019 proofs of ownership for native SegWit. diff --git a/legacy/firmware/.changelog.d/2718.added.2 b/legacy/firmware/.changelog.d/2718.added.2 deleted file mode 100644 index a7372ecde..000000000 --- a/legacy/firmware/.changelog.d/2718.added.2 +++ /dev/null @@ -1 +0,0 @@ -Implement `serialize` option in SignTx. diff --git a/legacy/firmware/.changelog.d/2718.added.3 b/legacy/firmware/.changelog.d/2718.added.3 deleted file mode 100644 index 38d0a16cc..000000000 --- a/legacy/firmware/.changelog.d/2718.added.3 +++ /dev/null @@ -1 +0,0 @@ -Implement SLIP-0025 coinjoin accounts. diff --git a/legacy/firmware/.changelog.d/2718.added.4 b/legacy/firmware/.changelog.d/2718.added.4 deleted file mode 100644 index 6146c8b2e..000000000 --- a/legacy/firmware/.changelog.d/2718.added.4 +++ /dev/null @@ -1 +0,0 @@ -Implement coinjoin signing. diff --git a/legacy/firmware/.changelog.d/2743.changed b/legacy/firmware/.changelog.d/2743.changed deleted file mode 100644 index 88dc53d70..000000000 --- a/legacy/firmware/.changelog.d/2743.changed +++ /dev/null @@ -1 +0,0 @@ -Increase 'SignIdentity.challenge_hidden' max_size to 512 bytes diff --git a/legacy/firmware/.changelog.d/noissue.security b/legacy/firmware/.changelog.d/noissue.security deleted file mode 100644 index 7318011cc..000000000 --- a/legacy/firmware/.changelog.d/noissue.security +++ /dev/null @@ -1 +0,0 @@ -Match and validate script type of change-outputs in Bitcoin signing. diff --git a/legacy/firmware/CHANGELOG.md b/legacy/firmware/CHANGELOG.md index b5d5bc974..9b967b742 100644 --- a/legacy/firmware/CHANGELOG.md +++ b/legacy/firmware/CHANGELOG.md @@ -4,24 +4,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [1.12.0] (internal only - January 2023) +## 1.12.1 [15th March 2023] ### Added -- Support Ledger Live legacy derivation path "m/44'/coin_type'/0'/account" [#1749] -- Show fee rate when replacing transaction [#2442] -- T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging [#2568] +- Support Ledger Live legacy derivation path `m/44'/coin_type'/0'/account`. [#1749] +- Show fee rate when replacing transaction. [#2442] +- T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging. [#2568] +- Allow proposed Casa m/45' multisig paths for Bitcoin and Ethereum. [#2682] +- Implement SLIP-0025 coinjoin accounts. [#2718] +- Implement `serialize` option in SignTx. [#2718] +- Support native SegWit external inputs with non-ownership proof. [#2718] +- Implement SLIP-0019 proofs of ownership for native SegWit. [#2718] +- Implement coinjoin signing. [#2718] ### Changed -- Do not convert bech32 addresses to uppercase in QR code to increase compatibility [#2190] -- Do not allow access to SLIP25 paths. [#2289] -- Extend decimals of fee rate to 2 digits [#2486] -- Display only sat instead of sat BTC [#2487] +- Do not convert bech32 addresses to uppercase in QR code to increase compatibility. [#2190] +- Extend decimals of fee rate to 2 digits. [#2486] +- Display only sat instead of sat BTC. [#2487] +- Increase `SignIdentity.challenge_hidden` max_size to 512 bytes. [#2743] +- Included bootloader 1.12.1. ### Fixed -- Bootloader VTOR and FW handover fix [#163] +- Bootloader VTOR and FW handover fix. [#163] +- Show full Stellar address and QR code. [#1453] +- Wrap long Ethereum fee to next line if it does not fit. [#2373] + +### Security +- Match and validate script type of change-outputs in Bitcoin signing. -## [1.11.2] (17th August 2022) +## 1.11.2 [17th August 2022] ### Added - Show the fee rate on the signing confirmation screen. [#2249] @@ -540,6 +552,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#1369]: https://github.com/trezor/trezor-firmware/pull/1369 [#1402]: https://github.com/trezor/trezor-firmware/pull/1402 [#1415]: https://github.com/trezor/trezor-firmware/pull/1415 +[#1453]: https://github.com/trezor/trezor-firmware/pull/1453 [#1461]: https://github.com/trezor/trezor-firmware/pull/1461 [#1491]: https://github.com/trezor/trezor-firmware/pull/1491 [#1518]: https://github.com/trezor/trezor-firmware/pull/1518 @@ -583,6 +596,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#2249]: https://github.com/trezor/trezor-firmware/pull/2249 [#2261]: https://github.com/trezor/trezor-firmware/pull/2261 [#2289]: https://github.com/trezor/trezor-firmware/pull/2289 +[#2373]: https://github.com/trezor/trezor-firmware/pull/2373 [#2394]: https://github.com/trezor/trezor-firmware/pull/2394 [#2422]: https://github.com/trezor/trezor-firmware/pull/2422 [#2433]: https://github.com/trezor/trezor-firmware/pull/2433 @@ -590,3 +604,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#2486]: https://github.com/trezor/trezor-firmware/pull/2486 [#2487]: https://github.com/trezor/trezor-firmware/pull/2487 [#2568]: https://github.com/trezor/trezor-firmware/pull/2568 +[#2682]: https://github.com/trezor/trezor-firmware/pull/2682 +[#2718]: https://github.com/trezor/trezor-firmware/pull/2718 +[#2743]: https://github.com/trezor/trezor-firmware/pull/2743 From 7430850da5258e5d19ae55bac94a31447a9d4d14 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 28 Feb 2023 17:39:32 +0100 Subject: [PATCH 28/30] chore(legacy/firmware): revert version to 1.12.1 [no changelog] --- legacy/firmware/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h index 77eccd0f5..2e77d533c 100644 --- a/legacy/firmware/version.h +++ b/legacy/firmware/version.h @@ -1,6 +1,6 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 12 -#define VERSION_PATCH 2 +#define VERSION_PATCH 1 #define FIX_VERSION_MAJOR 1 #define FIX_VERSION_MINOR 12 From 1eb0eb9d91b092e571aac63db4ebff2a07fd8a1f Mon Sep 17 00:00:00 2001 From: matejcik Date: Thu, 2 Mar 2023 11:28:00 +0100 Subject: [PATCH 29/30] fix(legacy): lower maximum of chars per line so that we actually fit if all the biggest characters are used [no changelog] (cherry picked from commit 770cd19899e603847c118a58513a82cf854fe0d5) --- .../fixtures/ethereum/sign_tx_eip1559.json | 10 +++++----- legacy/firmware/layout2.c | 2 +- tests/ui_tests/fixtures.json | 18 +++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/common/tests/fixtures/ethereum/sign_tx_eip1559.json b/common/tests/fixtures/ethereum/sign_tx_eip1559.json index d56253915..6f5918615 100644 --- a/common/tests/fixtures/ethereum/sign_tx_eip1559.json +++ b/common/tests/fixtures/ethereum/sign_tx_eip1559.json @@ -148,15 +148,15 @@ "to_address": "0x1d1c328764a41bda0492b66baa30c4a339ff85ef", "chain_id": 1, "nonce": "0x0", - "gas_limit": "0x141414141414141414", - "max_gas_fee": "0x14141414141414141414", - "max_priority_fee": "0x11111111111111111", + "gas_limit": "0xff", + "max_gas_fee": "0x15681d9b48e13b1e2", + "max_priority_fee": "0x6b14e9f812f366fb9", "value": "0xa" }, "result": { "sig_v": 0, - "sig_r": "3f3bfa6762b33819f268a98744803e1876aa440a6fd2ebef90cfd606bb893429", - "sig_s": "241e1128a715a5386c3b6d0998f9f42c21ee080568fbf2c642a05916c30737e2" + "sig_r": "e592ffedd8b802c95b634520f2b700f27e394ed47fe2555f2f378e26cdc2c1ff", + "sig_s": "0ad471ea5e6fdc90dc25c4a012f12d48bb3d5679cd5a39ad7d5bbfc26b5d333f" } } ] diff --git a/legacy/firmware/layout2.c b/legacy/firmware/layout2.c index 9d2c5f973..0b7b00a5d 100644 --- a/legacy/firmware/layout2.c +++ b/legacy/firmware/layout2.c @@ -263,7 +263,7 @@ void *layoutLast = NULL; void layoutDialogSwipeWrapping(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *heading, const char *description, const char *wrap_text) { - const uint32_t row_len = 20; + const uint32_t row_len = 18; const char **str = split_message((const uint8_t *)wrap_text, strlen(wrap_text), row_len); layoutDialogSwipe(icon, btnNo, btnYes, NULL, heading, description, str[0], diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 5998ae0a5..ccde9bb8d 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -427,7 +427,7 @@ "T1_ethereum-test_signtx.py::test_signtx_eip1559[data_2_bigdata]": "28d2a0d397b4e9865ca8286cc1a3669c3a7d34bb31b430662354bf3ec69d1cc5", "T1_ethereum-test_signtx.py::test_signtx_eip1559[known_erc20]": "b8e205a40711e377f2185cec6e085fa2f6a4d5bbc6a03d9cf2758058b6c1e17b", "T1_ethereum-test_signtx.py::test_signtx_eip1559[large_chainid]": "5b008a081b4ef9e7b47c9d0c1fc4bb24d729d77841193694232bd2da4a386515", -"T1_ethereum-test_signtx.py::test_signtx_eip1559[long_fees]": "ae698173acb069ced4d987735e53f8d46197dedb3d4b215c0dc02023ce8e986d", +"T1_ethereum-test_signtx.py::test_signtx_eip1559[long_fees]": "ef84e6b4ae6ff0b4c9d22a4b6356c8c67affbf28882e83cef4aea6e93b4bdcf5", "T1_ethereum-test_signtx.py::test_signtx_eip1559[nodata]": "5b008a081b4ef9e7b47c9d0c1fc4bb24d729d77841193694232bd2da4a386515", "T1_ethereum-test_signtx.py::test_signtx_eip1559[unknown_erc20]": "548c1f22918351e9cbcc1e16d8ba67bc2e7460b9a92cfc6c8bfa0a2b063e68da", "T1_ethereum-test_signtx.py::test_signtx_eip1559_access_list": "f6c5f398d4e80fc8f93cf70e9b10de24b9a968db04dc6ea21b28d1a273f04ca1", @@ -1457,14 +1457,14 @@ "TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_go_back]": "cb6d2a60c192b972e2cff02c4f7a6396184ba1a46df3c05b947e089f94427f0f", "TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_scroll_down]": "861ad7ab30bc8ea6edcc8138e86c654d04f9231dfd14ec1ca391820f145f7e57", "TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_skip]": "a8310b52786197219e3fb8399b19e624bbb74370a080c410daecc7f7422ea9cf", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[Ledger Live legacy path]": "66b381da23a9f31f962115de7a8ae95d575eda72f665d2d65f00bead78cdab48", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[data_1]": "5165979cd94b31b68ca478f2f230ee1470c74e2cf0cc80d53fc0278c41f34d1f", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[data_2_bigdata]": "1da940fdb1d80374ebb1eb3922ab15bccf54eaec6ed40ce83834a93695fa1311", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[known_erc20]": "0e43071011c080f3a64130113914a03285f35c0e9c69379f6c94eda48dc9816f", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[large_chainid]": "c2fa2b3fe49f369b7cf1da456cdd08fc56d232155aa48232ef5ba965df9cafaa", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[long_fees]": "dd272effd96a8ba69c2ef4ae3369963d03de957830e61de2a3f00e8ca7ba1f42", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[nodata]": "66b381da23a9f31f962115de7a8ae95d575eda72f665d2d65f00bead78cdab48", -"TT_ethereum-test_signtx.py::test_signtx_eip1559[unknown_erc20]": "541822da15b3d11952c9329092d38ba363ca021a0193967e441a2feb59b771fb", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[Ledger Live legacy path]": "11dde80cec962e0ed13d036455f72bdaba4480b5b80ef85255540727e497feed", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[data_1]": "9a1dfddc74a87f6ad67d45efeb9117918570d9f768019d8614f873389716af39", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[data_2_bigdata]": "2bd2220cabf0625a12aa7c0ad8dd9d4f03db0fc9c9af0e15579271334c8558bb", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[known_erc20]": "9af8e6fa7cf9411dab4be1dd4566788b08fa7fc0347e0a47580f51875ddd9a0b", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[large_chainid]": "19e9d757f4a896db3f06538cd5a5bcf6a54a354101634c336c9d09554a7e10e7", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[long_fees]": "709b2333c3811d957df9f068929a61c7e5ac95a87caeb1e2a6dcdb731045d840", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[nodata]": "11dde80cec962e0ed13d036455f72bdaba4480b5b80ef85255540727e497feed", +"TT_ethereum-test_signtx.py::test_signtx_eip1559[unknown_erc20]": "c021afc221550999f2842bce9be2f06cf46f69ab4155cbe0776e21f036ae21a9", "TT_ethereum-test_signtx.py::test_signtx_eip1559_access_list": "e32e0029dedc39857f3d8eb31ce8cf7a06d32e6ca81df7d67586fce123df4f7d", "TT_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "e32e0029dedc39857f3d8eb31ce8cf7a06d32e6ca81df7d67586fce123df4f7d", "TT_misc-test_cosi.py::test_cosi_different_key": "bd83a31d0fc4c23953dfd0d138e4441984e34698ace96aad5308a4ae51b712ae", From 5cf089fbd391c1ac9d1530e51327be940b0999e1 Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 6 Mar 2023 12:47:25 +0100 Subject: [PATCH 30/30] docs(legacy): remove wrong link from changelog --- legacy/firmware/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/legacy/firmware/CHANGELOG.md b/legacy/firmware/CHANGELOG.md index 9b967b742..0e0439721 100644 --- a/legacy/firmware/CHANGELOG.md +++ b/legacy/firmware/CHANGELOG.md @@ -25,7 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Included bootloader 1.12.1. ### Fixed -- Bootloader VTOR and FW handover fix. [#163] +- Bootloader VTOR and FW handover fix. - Show full Stellar address and QR code. [#1453] - Wrap long Ethereum fee to next line if it does not fit. [#2373] @@ -537,7 +537,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Removed all current limits on size of signed transaction. [#131]: https://github.com/trezor/trezor-firmware/pull/131 -[#163]: https://github.com/trezor/trezor-firmware/pull/163 [#965]: https://github.com/trezor/trezor-firmware/pull/965 [#1018]: https://github.com/trezor/trezor-firmware/pull/1018 [#1030]: https://github.com/trezor/trezor-firmware/pull/1030