feat(legacy): qa build for upgrade testing

release/23.03
tychovrahe 1 year ago committed by matejcik
parent 9d64039245
commit 3cd47f85af

@ -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)"

@ -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)

@ -0,0 +1 @@
../../../../legacy/firmware/bootloader.dat

@ -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

@ -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

@ -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("<I")
SIG_START = INDEXES_START_OLD + SLOTS + 1 + 52
def parse_args():
parser = argparse.ArgumentParser(
description="Commandline tool for signing Trezor firmware."
)
parser.add_argument("-f", "--file", dest="path", help="Firmware file to modify")
return parser.parse_args()
def pad_to_size(data, size):
if len(data) > 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("<L", data[12:16])[0]
assert size == len(data) - 1024
def check_signatures(data):
# Analyses given firmware and prints out
# status of included signatures
indexes = [x for x in data[INDEXES_START : INDEXES_START + SLOTS]]
to_sign = get_header(data, zero_signatures=True)
fingerprint = hashlib.sha256(to_sign).hexdigest()
print("Firmware fingerprint:", fingerprint)
used = []
for x in range(SLOTS):
signature = data[SIGNATURES_START + 64 * x : SIGNATURES_START + 64 * x + 64]
if indexes[x] == 0:
print(f"Slot #{x + 1}", "is empty")
else:
pubkeys = pubkeys_dev
pk = pubkeys[indexes[x]]
verify = ecdsa.VerifyingKey.from_string(
bytes.fromhex(pk)[1:],
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
try:
verify.verify(signature, to_sign, hashfunc=hashlib.sha256)
if indexes[x] in used:
print(f"Slot #{x + 1} signature: DUPLICATE", signature.hex())
else:
used.append(indexes[x])
print(f"Slot #{x + 1} signature: VALID", signature.hex())
except Exception:
print(f"Slot #{x + 1} signature: INVALID", signature.hex())
def modify(data, slot, index, signature):
data = bytearray(data)
# put index to data
data[INDEXES_START + slot - 1] = index
# put signature to data
data[SIGNATURES_START + 64 * (slot - 1) : SIGNATURES_START + 64 * slot] = signature
return bytes(data)
def sign(data, slot, secexp):
key = ecdsa.SigningKey.from_secret_exponent(
secexp=int(secexp, 16),
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
to_sign = get_header(data, zero_signatures=True)
# Locate proper index of current signing key
pubkey = "04" + key.get_verifying_key().to_string().hex()
index = None
pubkeys = pubkeys_dev
for i, pk in pubkeys.items():
if pk == pubkey:
index = i
break
if index is None:
raise Exception("Unable to find private key index. Unknown private key?")
signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256)
return modify(data, slot, index, signature)
def prepare_old(data):
# Takes raw OR signed firmware and clean out metadata structure
# This produces 'clean' data for signing
meta = b"TRZR" # magic
if data[:4] == b"TRZR":
meta += data[4 : 4 + struct.calcsize("<I")]
else:
meta += struct.pack("<I", len(data)) # length of the code
meta += b"\x00" * SLOTS # signature index #1-#3
meta += b"\x01" # flags
meta += b"\x00" * 52 # reserved
meta += b"\x00" * 64 * SLOTS # signature #1-#3
if data[:4] == b"TRZR":
# Replace existing header
out = meta + data[len(meta) :]
else:
# create data from meta + code
out = meta + data
return out
def check_signatures_old(data):
# Analyses given firmware and prints out
# status of included signatures
try:
indexes = [ord(x) for x in data[INDEXES_START_OLD : INDEXES_START_OLD + SLOTS]]
except TypeError:
indexes = [x for x in data[INDEXES_START_OLD : INDEXES_START_OLD + SLOTS]]
to_sign = prepare_old(data)[256:] # without meta
fingerprint = hashlib.sha256(to_sign).hexdigest()
print("Firmware fingerprint:", fingerprint)
used = []
for x in range(SLOTS):
signature = data[SIG_START + 64 * x : SIG_START + 64 * x + 64]
if indexes[x] == 0:
print("Slot #%d" % (x + 1), "is empty")
else:
pubkeys = pubkeys_dev
pk = pubkeys[indexes[x]]
verify = ecdsa.VerifyingKey.from_string(
bytes.fromhex(pk)[1:],
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
try:
verify.verify(signature, to_sign, hashfunc=hashlib.sha256)
if indexes[x] in used:
print("Slot #%d signature: DUPLICATE" % (x + 1), signature.hex())
else:
used.append(indexes[x])
print("Slot #%d signature: VALID" % (x + 1), signature.hex())
except BadSignatureError:
print("Slot #%d signature: INVALID" % (x + 1), signature.hex())
def modify_old(data, slot, index, signature):
# Replace signature in data
# Put index to data
data = (
data[: INDEXES_START_OLD + slot - 1]
+ bytes([index])
+ data[INDEXES_START_OLD + slot :]
)
# Put signature to data
data = (
data[: SIG_START + 64 * (slot - 1)] + signature + data[SIG_START + 64 * slot :]
)
return data
def sign_old(data, slot, secexp):
key = ecdsa.SigningKey.from_secret_exponent(
secexp=int(secexp, 16),
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256,
)
to_sign = prepare_old(data)[256:] # without meta
# Locate proper index of current signing key
pubkey = "04" + key.get_verifying_key().to_string().hex()
index = None
pubkeys = pubkeys_dev
for i, pk in pubkeys.items():
if pk == pubkey:
index = i
break
if index is None:
raise Exception("Unable to find private key index. Unknown private key?")
signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256)
return modify_old(data, slot, index, signature)
def main(args):
if not args.path:
raise Exception("-f/--file is required")
data = open(args.path, "rb").read()
assert len(data) % 4 == 0
if data[:4] != b"TRZF":
raise Exception("Firmware header expected")
data = update_hashes_in_header(data)
print(f"Firmware size {len(data)} bytes")
check_size(data)
check_signatures(data)
check_hashes(data)
data = sign(data, 1, privkeys_dev[1])
data = sign(data, 2, privkeys_dev[2])
data = sign(data, 3, privkeys_dev[3])
check_signatures(data)
check_hashes(data)
fp = open(args.path, "wb")
fp.write(data)
fp.close()
data = prepare_old(data)
check_signatures_old(data)
data = sign_old(data, 1, privkeys_dev[1])
data = sign_old(data, 2, privkeys_dev[2])
data = sign_old(data, 3, privkeys_dev[3])
check_signatures_old(data)
fp = open(args.path, "wb")
fp.write(data)
fp.close()
if __name__ == "__main__":
args = parse_args()
main(args)

@ -173,9 +173,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
header.o: version.h

@ -26,6 +26,81 @@
#include "memory.h"
#include "util.h"
#if BOOTLOADER_QA
static int known_bootloader(int r, const uint8_t *hash) {
if (r != 32) return 0;
if (0 ==
memcmp(hash,
"\x1b\xd8\x1c\x0a\x82\x20\x43\x28\xe9\xaf\x7b\xb6\xb1\x6b\x45\x27"
"\x81\x31\xcc\x76\x0a\xfa\x9f\xd0\x81\x6e\xc1\xe1\x12\x86\x49\x83",
32))
return 1; // 1.3.2
if (0 ==
memcmp(hash,
"\x19\x27\x59\xce\x15\x51\xc4\x21\x03\xcc\x9b\xe8\x4e\x06\x54\x24"
"\x68\x8a\x06\xfa\xbb\xe6\xb0\x19\x18\x11\xcd\xbc\x19\x6c\x2f\x1b",
32))
return 1; // 1.3.3
if (0 ==
memcmp(hash,
"\x09\x34\x2f\x51\x56\x58\xc1\xe5\x95\x64\x1c\x4f\x8d\xf1\xce\x2b"
"\x2b\x6a\x86\x39\x14\x35\x0a\x97\x0d\x6e\x4f\x37\xb1\xd7\xde\x65",
32))
return 1; // 1.4.0
if (0 ==
memcmp(hash,
"\x73\x37\xfa\x82\x2b\x62\x90\x50\x49\x3c\x6c\x39\x3a\x9f\x17\x41"
"\x75\xe8\x1b\x39\xf3\xb7\x1b\xff\xab\xa2\xb9\x79\x7f\xf1\x0a\x2b",
32))
return 1; // 1.5.0
if (0 ==
memcmp(hash,
"\x5c\x8b\x5e\xfc\x8d\x8f\x78\xf3\xa0\x23\x9d\x5f\x90\x95\x4d\xe7"
"\xe6\xb5\xb7\x09\xc9\x1c\x3a\xb2\xaa\x14\x6f\x0e\x26\x6e\x70\x60",
32))
return 1; // 1.5.1
if (0 ==
memcmp(hash,
"\xec\xbd\x06\xd3\x37\x25\xd5\xa6\xbe\xba\x2b\xe3\xde\xf4\x64\x7e"
"\xef\x5d\x7b\xb9\x2b\x0c\x19\x8d\xd2\x89\x47\x3b\xad\xd3\x4c\x97",
32))
return 1; // 1.6.0
if (0 ==
memcmp(hash,
"\xec\xca\xff\x24\x34\xdf\x3b\x49\xef\x00\x0c\x0f\x07\x1f\xa5\x60"
"\x18\x16\xfa\x19\x56\x0b\x23\xb4\x73\x52\x0e\x68\xc5\x2d\xe5\x7a",
32))
return 1; // 1.6.1
if (0 ==
memcmp(hash,
"\xe0\xc7\xfd\xb9\x1a\x14\xcb\x39\xd7\xc1\x43\xff\x70\xd2\x3a\x0b"
"\xfb\x0a\xef\xb5\x49\xb6\x15\x0a\xa9\x09\xf8\x35\x0a\xa5\xeb\xa2",
32))
return 1; // 1.8.0
if (0 ==
memcmp(hash,
"\xf9\x9f\x49\xf8\xd0\xc3\xfa\x82\xd6\xad\x1e\xf4\xf2\xf2\xd7\x2d"
"\x01\xa5\xdc\xa3\x6f\x11\xba\xb9\x13\xbd\xfe\xaf\x84\xb2\x6f\x4a",
32))
return 1; // 1.10.0
if (0 ==
memcmp(hash,
"\x8f\x83\x57\x70\x11\x75\xaf\x5c\x1a\xe5\xb9\x6f\x13\x42\x2f\x7f"
"\x1a\x52\xed\x96\xcd\xa0\x18\x07\x63\x0e\x0d\x25\x6c\xd9\x18\x78",
32))
return 1; // 1.11.0
// 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",
32))
return 1; // 1.12.0 shipped with fw 1.12.0
// END AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt)
return 0;
}
#endif
#if PRODUCTION
static int known_bootloader(int r, const uint8_t *hash) {
@ -138,6 +213,8 @@ static int known_bootloader(int r, const uint8_t *hash) {
"\xe4\xc3\x21\x9c\x9f\x1b\xb3\xa5\x77\x2f\xfd\x60\x9a\xf9\xe8\xe2",
32))
return 1; // 1.11.0 shipped with fw 1.11.1
// BEGIN AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)
// END AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)
return 0;
}
#endif
@ -149,7 +226,7 @@ static int known_bootloader(int r, const uint8_t *hash) {
* @param shutdown_on_replace: if true, shuts down device instead of return
*/
void check_and_replace_bootloader(bool shutdown_on_replace) {
#if PRODUCTION
#if PRODUCTION || BOOTLOADER_QA
uint8_t hash[32] = {0};
int r = memory_bootloader_hash(hash);

@ -0,0 +1 @@
2934f3d3aa592b34496a23b81474e2c2f3ee1e36660bf01e198a6515c232c185 1.12.0 shipped with fw 1.12.0

@ -1,7 +1,10 @@
#!/usr/bin/env python
import sys
from hashlib import sha256
fn = "bootloader.dat"
fn = sys.argv[1]
print("Embedding bootloader", fn)
data = open(fn, "rb").read()
if len(data) > 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")

Binary file not shown.

@ -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

@ -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

@ -0,0 +1 @@
../firmware/bootloader_qa.dat

@ -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) {

@ -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__":

@ -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;

Loading…
Cancel
Save