Merge branch 'release/23.03'

Conflicts:
	legacy/firmware/version.h
	tests/ui_tests/fixtures.json
pull/2887/head
Martin Milata 1 year ago
commit dce876069b

@ -41,27 +41,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}

@ -21,6 +21,7 @@ CROSS_PORT_OPTS ?=
PRODUCTION ?= 0
PYOPT ?= 1
BITCOIN_ONLY ?= 0
BOOTLOADER_QA ?= 0
TREZOR_MODEL ?= T
TREZOR_MEMPERF ?= 0
ADDRESS_SANITIZER ?= 0
@ -178,7 +179,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)" CMAKELISTS="$(CMAKELISTS)" PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" $(FIRMWARE_BUILD_DIR)/firmware.bin
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" 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)" CMAKELISTS="$(CMAKELISTS)" PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)"

@ -5,11 +5,16 @@ 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', )
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
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)
@ -424,7 +429,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))
FEATURES_AVAILABLE = tools.configure_board(TREZOR_MODEL, FEATURES_WANTED, env, CPPDEFINES_MOD, SOURCE_TREZORHAL)
@ -782,7 +787,7 @@ obj_program.extend(
' $SOURCE $TARGET', ))
BOOTLOADER_SUFFIX = tools.get_model_identifier(TREZOR_MODEL)
BOOTLOADER_SUFFIX = tools.get_model_identifier(TREZOR_MODEL) + ('_QA' if BOOTLOADER_QA else '')
obj_program.extend(
env.Command(

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

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

@ -1,6 +1,5 @@
ifneq ($(EMULATOR),1)
OBJS += setup.o
OBJS += startup.o
OBJS += timer.o
endif

@ -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
@ -156,6 +163,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

@ -1 +0,0 @@
T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging

@ -4,6 +4,21 @@ 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.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]
### Fixed
- Better way to debug T1 combinations of debug/production combinations of bootloader and firmware [#2423]
## 1.11.0 [May 2022]
### Added
@ -128,3 +143,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

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

@ -161,6 +161,12 @@ int main(void) {
if (SIG_OK != signed_firmware) {
show_unofficial_warning(fingerprint);
}
#if !PRODUCTION && !BOOTLOADER_QA && !DEBUG_T1_SIGNATURES
// try to avoid bricking board SWD debug by accident
else {
show_halt("Official firmware", "Won't run on debug device");
}
#endif
if (SIG_OK != check_firmware_hashes(hdr)) {
show_halt("Broken firmware", "detected.");

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

@ -20,18 +20,6 @@ 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
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
#endif
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

@ -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;
}
@ -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, true)) {
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;
}

@ -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"
#define VERSION_MINOR 12
#define VERSION_PATCH 2

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

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

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

@ -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",
]

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

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

@ -0,0 +1 @@
../bootloader/startup.S

@ -1 +0,0 @@
Show full Stellar address and QR code.

@ -1 +0,0 @@
Support Ledger Live legacy derivation path "m/44'/coin_type'/0'/account"

@ -1 +0,0 @@
Do not convert bech32 addresses to uppercase in QR code to increase compatibility

@ -1 +0,0 @@
Do not allow access to SLIP25 paths.

@ -1 +0,0 @@
Wrap long Ethereum fee to next line if it does not fit.

@ -1 +0,0 @@
Show fee rate when replacing transaction

@ -1 +0,0 @@
Extend decimals of fee rate to 2 digits

@ -1 +0,0 @@
Display only sat instead of sat BTC

@ -1 +0,0 @@
T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging

@ -1 +0,0 @@
Allow proposed Casa m/45' multisig paths for Bitcoin and Ethereum.

@ -1 +0,0 @@
Support native SegWit external inputs with non-ownership proof.

@ -1 +0,0 @@
Implement SLIP-0019 proofs of ownership for native SegWit.

@ -1 +0,0 @@
Implement `serialize` option in SignTx.

@ -1 +0,0 @@
Implement SLIP-0025 coinjoin accounts.

@ -1 +0,0 @@
Implement coinjoin signing.

@ -1 +0,0 @@
Increase 'SignIdentity.challenge_hidden' max_size to 512 bytes

@ -1 +0,0 @@
Match and validate script type of change-outputs in Bitcoin signing.

@ -4,7 +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.11.2] (17th August 2022)
## 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]
- 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]
- 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.
- 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]
### Added
- Show the fee rate on the signing confirmation screen. [#2249]
@ -522,6 +551,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
@ -538,6 +568,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 +590,19 @@ 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
[#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
[#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
[#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

@ -40,6 +40,7 @@ OBJS += usb.o
OBJS += bl_check.o
OBJS += otp.o
OBJS += header.o
OBJS += startup.o
endif
OBJS += messages.o
@ -172,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,87 @@
#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,
"\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
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;
}
#endif
#if PRODUCTION
static int known_bootloader(int r, const uint8_t *hash) {
@ -138,6 +219,20 @@ 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)
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
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;
}
#endif
@ -149,7 +244,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,2 @@
e9ec8fa2fefad2b3b6b7c4ab76691a3361b3eefb8a0ddaa34d7e45b63b3d5664 1.12.0 shipped with fw 1.12.0
94f1c90db28db1f8ce5dca966976343658f5dadee83834987c8b049c49d1edd0 1.12.1 shipped with fw 1.12.1

@ -0,0 +1,2 @@
21b949f4f5fd9f3a7d6343d1076f960fb45418196531b9f2974a68ede8db2ea1 1.12.0 shipped with fw 1.12.0
b4e59244185ce1cd6c8f590310370218509c393226f0074b8cf6adedb3cd4d55 1.12.1 shipped with fw 1.12.1

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

Binary file not shown.

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

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

@ -0,0 +1,107 @@
.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:
// 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
// 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
// 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
beq .setup_as_unprivileged
ldr r0, =0xE000ED08 // r0 = VTOR address
ldr r1, =0x08010400 // r1 = FLASH_APP_START
str r1, [r0] // assign
dsb
isb
.setup_as_unprivileged:
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

@ -3,5 +3,5 @@
#define VERSION_PATCH 2
#define FIX_VERSION_MAJOR 1
#define FIX_VERSION_MINOR 11
#define FIX_VERSION_MINOR 12
#define FIX_VERSION_PATCH 0

@ -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,12 +56,10 @@ 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
@ -68,35 +68,34 @@ static const uint8_t * const pubkey_v3[PUBKEYS] = {
Debug private keys for v2 (previously called "new") scheme
corresponding to pubkeys below as python hexstring array:
['ba7994923c91771ad77c483f7d2b41f5506b82aa900e6f12edeae96c5c9f8f66',
'81a825d359da7ec9534e6cf7dd190bdbad62e134265764a5ec3e63317b060a51',
'37107a021e50ca3571102691606083f6a8d9cd600e35cd2c8e8f7b87796a045b',
['4444444444444444444444444444444444444444444444444444444444444444',
'4545454545454545454545454545454545454545454545454545454545454545',
'bfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8',
'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"
};
#else
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_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",
@ -105,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)
@ -171,28 +168,36 @@ 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)
return SIG_FAIL; // invalid index
if (hdr->sigindex2 < 1 || hdr->sigindex2 > PUBKEYS)
if (hdr->sigindex1 < 1 || hdr->sigindex1 > pubkeys)
return SIG_FAIL; // invalid index
if (hdr->sigindex3 < 1 || hdr->sigindex3 > PUBKEYS)
if (hdr->sigindex2 < 1 || hdr->sigindex2 > 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;
}
if (hdr->sigindex1 == hdr->sigindex2) return SIG_FAIL; // duplicate use
if (hdr->sigindex1 == hdr->sigindex3) return SIG_FAIL; // duplicate use
@ -206,9 +211,18 @@ 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
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) {
return SIG_FAIL;
}
}
}
return SIG_OK;
@ -221,8 +235,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;
}

@ -22,6 +22,7 @@
#include <stdbool.h>
#include <stdint.h>
#include "secbool.h"
extern const uint32_t FIRMWARE_MAGIC_NEW; // TRZF
@ -43,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];
@ -105,7 +109,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.

@ -0,0 +1 @@
Bootloader VTOR and FW handover fix

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

@ -0,0 +1 @@
../firmware/startup.S

@ -1,8 +1,8 @@
// Matches the bootloader version included in this firmware
#define VERSION_MAJOR 1
#define VERSION_MINOR 10
#define VERSION_PATCH 0
#define VERSION_MINOR 12
#define VERSION_PATCH 1
#define FIX_VERSION_MAJOR 1
#define FIX_VERSION_MINOR 10
#define FIX_VERSION_MINOR 12
#define FIX_VERSION_PATCH 0

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

@ -0,0 +1,166 @@
#!/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_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 ==
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"
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.
>>> 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(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 txt_file.read_text().splitlines()
)
}
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 == 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 == 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.")
@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
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())
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])
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(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(), begin, end)
click.echo("Regenerated bl_check.c")
# overwrite bootloader.dat
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__":
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;

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

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

@ -1,5 +1,5 @@
## [{{ versiondata.version }}] ({{versiondata.date}})
## {{ versiondata.version }} [{{versiondata.date}}]
{% for section, _ in sections.items() %}
{% if section %}{{section}}{% endif -%}
{% if sections[section] %}

Loading…
Cancel
Save