Conflicts: legacy/firmware/version.h tests/ui_tests/fixtures.jsonpull/2887/head
commit
dce876069b
@ -0,0 +1 @@
|
||||
../../../../legacy/firmware/bootloader.dat
|
@ -1 +0,0 @@
|
||||
T1 bootloader: verify firmware signatures based on SignMessage, add signature debugging
|
@ -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)
|
@ -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
|
||||
|
@ -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()
|
@ -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.
|
@ -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
|
Binary file not shown.
Binary file not shown.
@ -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
|
@ -0,0 +1 @@
|
||||
Bootloader VTOR and FW handover fix
|
@ -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
|
||||
|
@ -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()
|
Loading…
Reference in new issue