mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-17 05:03:07 +00:00
feat(legacy): qa build for upgrade testing
This commit is contained in:
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)
|
||||
|
1
core/embed/firmware/bootloaders/bootloader_1.bin
Symbolic link
1
core/embed/firmware/bootloaders/bootloader_1.bin
Symbolic link
@ -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
|
||||
|
||||
|
333
legacy/bootloader/firmware_sign_dev.py
Normal file
333
legacy/bootloader/firmware_sign_dev.py
Normal file
@ -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);
|
||||
|
||||
|
1
legacy/firmware/bl_check_qa.txt
Normal file
1
legacy/firmware/bl_check_qa.txt
Normal file
@ -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")
|
||||
|
BIN
legacy/firmware/bootloader_qa.dat
Normal file
BIN
legacy/firmware/bootloader_qa.dat
Normal file
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
|
||||
|
1
legacy/intermediate_fw/bootloader_qa.dat
Symbolic link
1
legacy/intermediate_fw/bootloader_qa.dat
Symbolic link
@ -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…
Reference in New Issue
Block a user