1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-29 18:08:19 +00:00

feat(core): verify secmon signature in bootloader

[no changelog]
This commit is contained in:
tychovrahe 2025-06-15 19:33:59 +02:00 committed by TychoVrahe
parent fcf2bd0d48
commit 8795cbcf5c
9 changed files with 380 additions and 9 deletions

View File

@ -338,9 +338,37 @@ void real_jump_to_firmware(void) {
&FIRMWARE_AREA),
"Firmware is corrupted");
secret_prepare_fw(
((vhdr.vtrust & VTRUST_SECRET_MASK) == VTRUST_SECRET_ALLOW) * sectrue,
((vhdr.vtrust & VTRUST_NO_WARNING) == VTRUST_NO_WARNING) * sectrue);
size_t secmon_code_offset = 0;
#ifdef USE_SECMON_VERIFICATION
size_t secmon_start = (size_t)IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen +
IMAGE_HEADER_SIZE);
const secmon_header_t *secmon_hdr =
read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
if (secmon_hdr != NULL) {
secmon_code_offset = IMAGE_CODE_ALIGN(SECMON_HEADER_SIZE);
}
ensure((secmon_hdr != NULL) * sectrue, "Secmon header not found");
ensure(check_secmon_model(secmon_hdr), "Wrong secmon model");
ensure(check_secmon_header_sig(secmon_hdr), "Invalid secmon signature");
ensure(check_secmon_contents(secmon_hdr, secmon_start - FIRMWARE_START,
&FIRMWARE_AREA),
"Secmon is corrupted");
#endif
secbool provisioning_access =
((vhdr.vtrust & VTRUST_ALLOW_PROVISIONING) == VTRUST_ALLOW_PROVISIONING) *
sectrue;
secbool secret_run_access =
((vhdr.vtrust & VTRUST_SECRET_MASK) == VTRUST_SECRET_ALLOW) * sectrue;
secret_prepare_fw(secret_run_access, provisioning_access);
// if all warnings are disabled in VTRUST flags then skip the procedure
if ((vhdr.vtrust & VTRUST_NO_WARNING) != VTRUST_NO_WARNING) {
@ -381,7 +409,8 @@ void real_jump_to_firmware(void) {
system_deinit();
jump_to_next_stage(
IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE));
IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE) +
secmon_code_offset);
}
__attribute__((noreturn)) void reboot_with_fade(void) {
@ -398,7 +427,11 @@ int bootloader_main(void) {
system_init(&rsod_panic_handler);
secbool manufacturing_mode = is_manufacturing_mode();
vendor_header vhdr;
volatile secbool vhdr_present = secfalse;
vhdr_present = read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr);
secbool manufacturing_mode = is_manufacturing_mode(&vhdr);
secbool stay_in_bootloader = boot_sequence(manufacturing_mode);
@ -426,10 +459,8 @@ int bootloader_main(void) {
#endif
const image_header *hdr = NULL;
vendor_header vhdr;
// detect whether the device contains a valid firmware
volatile secbool vhdr_present = secfalse;
volatile secbool vhdr_keys_ok = secfalse;
volatile secbool vhdr_lock_ok = secfalse;
volatile secbool img_hdr_ok = secfalse;
@ -440,8 +471,7 @@ int bootloader_main(void) {
volatile secbool firmware_present = secfalse;
volatile secbool firmware_present_backup = secfalse;
volatile secbool auto_upgrade = secfalse;
vhdr_present = read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr);
volatile secbool secmon_valid = secfalse;
if (sectrue == vhdr_present) {
vhdr_keys_ok = check_vendor_header_keys(&vhdr);
@ -476,7 +506,46 @@ int bootloader_main(void) {
header_present = version_ok;
}
#ifdef USE_SECMON_VERIFICATION
size_t secmon_start = (size_t)IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen +
IMAGE_HEADER_SIZE);
const secmon_header_t *secmon_hdr =
read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
volatile secbool secmon_header_present = secfalse;
volatile secbool secmon_model_valid = secfalse;
volatile secbool secmon_header_sig_valid = secfalse;
volatile secbool secmon_contents_valid = secfalse;
if (sectrue == header_present) {
secmon_header_present =
secbool_and(header_present, (secmon_hdr != NULL) * sectrue);
}
if (sectrue == secmon_header_present) {
secmon_model_valid =
secbool_and(secmon_header_present, check_secmon_model(secmon_hdr));
}
if (sectrue == secmon_model_valid) {
secmon_header_sig_valid =
secbool_and(secmon_model_valid, check_secmon_header_sig(secmon_hdr));
}
if (sectrue == secmon_header_sig_valid) {
secmon_contents_valid = secbool_and(
secmon_header_sig_valid,
check_secmon_contents(secmon_hdr, secmon_start - FIRMWARE_START,
&FIRMWARE_AREA));
secmon_valid = secmon_contents_valid;
}
#else
secmon_valid = header_present;
#endif
if (sectrue == secmon_valid) {
ensure_firmware_min_version(hdr->monotonic);
firmware_present = check_image_contents(
hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, &FIRMWARE_AREA);

View File

@ -62,6 +62,11 @@ typedef enum {
UPLOAD_ERR_NOT_FULLTRUST_IMAGE = -13,
UPLOAD_ERR_INVALID_CHUNK_PADDING = -14,
UPLOAD_ERR_COMMUNICATION = -17,
UPLOAD_ERR_INVALID_SECMON_HEADER = -18,
UPLOAD_ERR_INVALID_SECMON_HEADER_SIG = -19,
UPLOAD_ERR_INVALID_SECMON_MODEL = -20,
UPLOAD_ERR_INVALID_SECMON_HASH = -21,
UPLOAD_ERR_SECMON_TOO_BIG = -22,
} upload_status_t;
#define FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT 2
@ -80,6 +85,19 @@ typedef struct {
size_t headers_offset; // offset of headers in the first block
size_t read_offset; // offset of the next read data in the chunk buffer
uint32_t chunk_size; // size of already received chunk data
#ifdef USE_SECMON_VERIFICATION
size_t secmon_code_offset; // offset of the secmon code in the first block
size_t secmon_code_size; // size of the secmon code
size_t secmon_code_processed; // size of the processed secmon code
uint8_t expected_secmon_hash[IMAGE_HASH_DIGEST_LENGTH]; // expected hash of
// the secmon code
// todo should be IMAGE_HASH_CTX, but due to limitations of the hash_processor
// driver implementation, we can't run two hash calculations in parallel so
// temporarily we use SW hashing for secmon code during update.
// As secmon is only used on U5 MCUs where also SHA256is used for image
// hashes, this works, but should be fixed by improving the hash_processor
SHA256_CTX secmon_hash_ctx;
#endif
} firmware_update_ctx_t;
static int version_compare(uint32_t vera, uint32_t verb) {
@ -233,6 +251,43 @@ static upload_status_t process_msg_FirmwareUpload(protob_io_t *iface,
return UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION;
}
#ifdef USE_SECMON_VERIFICATION
size_t secmon_start_offset =
(size_t)IMAGE_CODE_ALIGN(vhdr.hdrlen + IMAGE_HEADER_SIZE);
size_t secmon_start = (size_t)chunk_buffer + secmon_start_offset;
const secmon_header_t *secmon_hdr =
read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
if (secmon_hdr != NULL) {
ctx->secmon_code_offset =
IMAGE_CODE_ALIGN(vhdr.hdrlen + IMAGE_HEADER_SIZE) +
SECMON_HEADER_SIZE;
}
if (secmon_hdr != (const secmon_header_t *)secmon_start) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Invalid secmon header");
return UPLOAD_ERR_INVALID_SECMON_HEADER;
}
if (sectrue != check_secmon_model(secmon_hdr)) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Wrong secmon model");
return UPLOAD_ERR_INVALID_SECMON_MODEL;
}
if (sectrue != check_secmon_header_sig(secmon_hdr)) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Invalid secmon signature");
return UPLOAD_ERR_INVALID_SECMON_HEADER_SIG;
}
ctx->secmon_code_size = secmon_hdr->codelen;
memcpy(ctx->expected_secmon_hash, secmon_hdr->hash,
IMAGE_HASH_DIGEST_LENGTH);
#endif
memcpy(&hdr, received_hdr, sizeof(hdr));
vendor_header current_vhdr;
@ -313,6 +368,16 @@ static upload_status_t process_msg_FirmwareUpload(protob_io_t *iface,
}
#endif
#ifdef USE_SECMON_VERIFICATION
if (ctx->secmon_code_size >
((hdr.codelen + IMAGE_HEADER_SIZE + vhdr.hdrlen) -
ctx->secmon_code_offset)) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Secmon code too big");
return UPLOAD_ERR_SECMON_TOO_BIG;
}
#endif
confirm_result_t response = CANCEL;
if (((vhdr.vtrust & VTRUST_NO_WARNING) == VTRUST_NO_WARNING) &&
(sectrue == is_new || sectrue == is_ilu)) {
@ -408,6 +473,46 @@ static upload_status_t process_msg_FirmwareUpload(protob_io_t *iface,
return UPLOAD_ERR_INVALID_CHUNK_HASH;
}
#ifdef USE_SECMON_VERIFICATION
// validate secmon code hash
if (ctx->secmon_code_size > 0) {
if (ctx->secmon_code_processed == 0) {
// todo SW SHA256, see comment in firmware_update_ctx_t
sha256_Init(&ctx->secmon_hash_ctx);
}
size_t secmon_code_remaining =
ctx->secmon_code_size - ctx->secmon_code_processed;
size_t secmon_code_to_process = IMAGE_CHUNK_SIZE - ctx->secmon_code_offset;
secmon_code_to_process = MIN(secmon_code_to_process, secmon_code_remaining);
sha256_Update(&ctx->secmon_hash_ctx,
(uint8_t *)chunk_buffer + ctx->secmon_code_offset,
secmon_code_to_process);
ctx->secmon_code_processed += secmon_code_to_process;
ctx->secmon_code_offset = 0;
if (ctx->secmon_code_processed >= ctx->secmon_code_size) {
// secmon code is fully processed
uint8_t secmon_hash[IMAGE_HASH_DIGEST_LENGTH];
sha256_Final(&ctx->secmon_hash_ctx, secmon_hash);
if (memcmp(secmon_hash, ctx->expected_secmon_hash,
IMAGE_HASH_DIGEST_LENGTH) != 0) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Invalid secmon hash");
return UPLOAD_ERR_INVALID_SECMON_HASH;
}
ctx->secmon_code_size = 0; // reset secmon code size to prevent
// reprocessing in the next chunk
}
}
#endif
// buffer with the received data
const uint32_t *src = (const uint32_t *)chunk_buffer;
// number of received bytes

View File

@ -54,6 +54,19 @@ static const uint8_t * const BOOTLOADER_KEYS[] = {
#endif
};
const uint8_t SECMON_KEY_M = 2;
const uint8_t SECMON_KEY_N = 3;
static const uint8_t * const SECMON_KEYS[] = {
#if !PRODUCTION
/*** DEVEL/QA KEYS ***/
(const uint8_t *)"\xdb\x99\x5f\xe2\x51\x69\xd1\x41\xca\xb9\xbb\xba\x92\xba\xa0\x1f\x9f\x2e\x1e\xce\x7d\xf4\xcb\x2a\xc0\x51\x90\xf3\x7f\xcc\x1f\x9d",
(const uint8_t *)"\x21\x52\xf8\xd1\x9b\x79\x1d\x24\x45\x32\x42\xe1\x5f\x2e\xab\x6c\xb7\xcf\xfa\x7b\x6a\x5e\xd3\x00\x97\x96\x0e\x06\x98\x81\xdb\x12",
(const uint8_t *)"\x22\xfc\x29\x77\x92\xf0\xb6\xff\xc0\xbf\xcf\xdb\x7e\xdb\x0c\x0a\xa1\x4e\x02\x5a\x36\x5e\xc0\xe3\x42\xe8\x6e\x38\x29\xcb\x74\xb6",
#else
MODEL_SCMON_KEYS
#endif
};
static secbool compute_pubkey(uint8_t sig_m, uint8_t sig_n,
const uint8_t *const *pub, uint8_t sigmask,
ed25519_public_key res) {
@ -159,6 +172,84 @@ secbool check_image_header_sig(const image_header *const hdr, uint8_t key_m,
*(const ed25519_signature *)hdr->sig));
}
const secmon_header_t *read_secmon_header(const uint8_t *const data,
const uint32_t maxsize) {
const secmon_header_t *hdr = (const secmon_header_t *)data;
if (hdr->magic != SECMON_IMAGE_MAGIC) {
return NULL;
}
if (hdr->hdrlen != SECMON_HEADER_SIZE) {
return NULL;
}
if (hdr->codelen > (maxsize - hdr->hdrlen)) return secfalse;
if ((hdr->hdrlen + hdr->codelen) < 4 * 1024) return secfalse;
if ((hdr->hdrlen + hdr->codelen) % 512 != 0) return secfalse;
return hdr;
}
secbool check_secmon_model(const secmon_header_t *const hdr) {
#ifndef TREZOR_EMULATOR
if (hdr->hw_model != HW_MODEL) {
return secfalse;
}
if (hdr->hw_revision != HW_REVISION) {
return secfalse;
}
#endif
return sectrue;
}
void get_secmon_fingerprint(const secmon_header_t *const hdr,
uint8_t *const out) {
IMAGE_HASH_CTX ctx;
IMAGE_HASH_INIT(&ctx);
IMAGE_HASH_UPDATE(&ctx, (uint8_t *)hdr, SECMON_HEADER_SIZE - IMAGE_SIG_SIZE);
for (int i = 0; i < IMAGE_SIG_SIZE; i++) {
IMAGE_HASH_UPDATE(&ctx, (const uint8_t *)"\x00", 1);
}
IMAGE_HASH_FINAL(&ctx, out);
}
secbool check_secmon_header_sig(const secmon_header_t *const hdr) {
// check header signature
uint8_t fingerprint[32];
get_secmon_fingerprint(hdr, fingerprint);
ed25519_public_key pub;
if (sectrue != compute_pubkey(SECMON_KEY_M, SECMON_KEY_N, SECMON_KEYS,
hdr->sigmask, pub))
return secfalse;
return sectrue *
(0 == ed25519_sign_open(fingerprint, IMAGE_HASH_DIGEST_LENGTH, pub,
*(const ed25519_signature *)hdr->sig));
}
secbool check_secmon_contents(const secmon_header_t *const hdr,
size_t code_offset, const flash_area_t *area) {
if (0 == area) {
return secfalse;
}
// Check the secmon integrity, calculate and compare hash
const void *data = flash_area_get_address(
area, code_offset + SECMON_HEADER_SIZE, hdr->codelen);
if (!data) {
return secfalse;
}
if (sectrue != check_single_hash(hdr->hash, data, hdr->codelen)) {
return secfalse;
}
return sectrue;
}
secbool __wur read_vendor_header(const uint8_t *const data,
vendor_header *const vhdr) {
memcpy(&vhdr->magic, data, 4);

View File

@ -29,6 +29,7 @@
#define VENDOR_HEADER_MAX_SIZE (64 * 1024)
#define IMAGE_HEADER_SIZE 0x400 // size of the bootloader or firmware header
#define SECMON_HEADER_SIZE 0x200
#define IMAGE_SIG_SIZE 65
#define IMAGE_INIT_CHUNK_SIZE (16 * 1024)
@ -36,6 +37,8 @@
#define FIRMWARE_IMAGE_MAGIC 0x465A5254 // TRZF
#define SECMON_IMAGE_MAGIC 0x43455354 // TSEC
#define IMAGE_CODE_ALIGN(addr) \
((((uint32_t)(uintptr_t)addr) + (CODE_ALIGNMENT - 1)) & ~(CODE_ALIGNMENT - 1))
@ -120,6 +123,20 @@ typedef struct {
uint8_t hash[IMAGE_HASH_DIGEST_LENGTH];
} firmware_header_info_t;
typedef struct {
uint32_t magic;
uint32_t hdrlen;
uint32_t codelen;
uint32_t version;
uint32_t hw_model;
uint8_t hw_revision;
uint8_t reserved_0[3];
uint8_t hash[32];
uint8_t reserved_1[391];
uint8_t sigmask;
uint8_t sig[64];
} secmon_header_t;
const image_header *read_image_header(const uint8_t *const data,
const uint32_t magic,
const uint32_t maxsize);
@ -156,3 +173,14 @@ secbool check_firmware_header(const uint8_t *header, size_t header_size,
firmware_header_info_t *info);
secbool __wur check_bootloader_header_sig(const image_header *const hdr);
const secmon_header_t *read_secmon_header(const uint8_t *const data,
const uint32_t maxsize);
secbool __wur check_secmon_model(const secmon_header_t *const hdr);
secbool __wur check_secmon_header_sig(const secmon_header_t *const hdr);
secbool __wur check_secmon_contents(const secmon_header_t *const hdr,
size_t code_offset,
const flash_area_t *area);

View File

@ -47,6 +47,7 @@ def configure(
("USE_HSE", "1"),
("USE_BOOTARGS_RSOD", "1"),
("LOCKABLE_BOOTLOADER", "1"),
("USE_SECMON_VERIFICATION", "1"),
]
if "display" in features_wanted:

View File

@ -49,6 +49,7 @@ def configure(
("FIXED_HW_DEINIT", "1"),
("LOCKABLE_BOOTLOADER", "1"),
("LAZY_DISPLAY_INIT", "1"),
("USE_SECMON_VERIFICATION", "1"),
("USE_BOOTARGS_RSOD", "1"),
("TERMINAL_FONT_SCALE", "2"),
("TERMINAL_X_PADDING", "4"),

View File

@ -49,6 +49,7 @@ def configure(
("FIXED_HW_DEINIT", "1"),
("LOCKABLE_BOOTLOADER", "1"),
("LAZY_DISPLAY_INIT", "1"),
("USE_SECMON_VERIFICATION", "1"),
("USE_BOOTARGS_RSOD", "1"),
("TERMINAL_FONT_SCALE", "2"),
("TERMINAL_X_PADDING", "4"),

View File

@ -49,6 +49,7 @@ def configure(
("FIXED_HW_DEINIT", "1"),
("LOCKABLE_BOOTLOADER", "1"),
("LAZY_DISPLAY_INIT", "1"),
("USE_SECMON_VERIFICATION", "1"),
("USE_BOOTARGS_RSOD", "1"),
("TERMINAL_FONT_SCALE", "2"),
("TERMINAL_X_PADDING", "4"),

View File

@ -183,6 +183,42 @@ def format_header(
return "\n".join(output)
def format_secmon_header(
header: firmware.secmon.SecmonHeader,
code_hash: bytes,
digest: bytes,
sig_status: Status,
) -> str:
header_dict = asdict(header)
header_out = header_dict.copy()
for key, val in header_out.items():
if "version" in key:
header_out[key] = LiteralStr(_format_version(val))
# status = SYM_OK if header.hash == code_hash else SYM_FAIL
if all_zero(header.hash):
hash_status = Status.MISSING
elif header.hash != code_hash:
hash_status = Status.INVALID
else:
hash_status = Status.VALID
#
# header_out["hashes"] = hashes_out
all_ok = SYM_OK if hash_status.is_ok() and sig_status.is_ok() else SYM_FAIL
output = [
"SECMON Header " + _format_container(header_out),
"Code hash: " + click.style(code_hash.hex(), bold=True),
f"Fingerprint: {click.style(digest.hex(), bold=True)}",
f"{all_ok} Signature is {sig_status.value}, hash is {hash_status.value}",
]
return "\n".join(output)
# =========================== functionality implementations ===============
@ -391,6 +427,39 @@ class BootloaderImage(firmware.FirmwareImage, CosiSignedMixin):
return self.get_model_keys(dev_keys).boardloader_keys
class SecmonImage(firmware.SecmonImage, CosiSignedMixin):
NAME: t.ClassVar[str] = "secmon"
DEV_KEYS = _make_dev_keys(b"\x41", b"\x42")
def get_header(self) -> CosiSignatureHeaderProto:
return self.header
def format(self, verbose: bool = False) -> str:
return format_secmon_header(
self.header,
self.code_hash(),
self.digest(),
_check_signature_any(self),
)
def verify(self, dev_keys: bool = False) -> None:
self.validate_code_hash()
public_keys = self.public_keys(dev_keys)
try:
cosi.verify(
self.header.signature,
self.digest(),
self.get_model_keys(dev_keys).boardloader_sigs_needed,
public_keys,
self.header.sigmask,
)
except Exception:
raise firmware.InvalidSignatureError("Invalid bootloader signature")
def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]:
return self.get_model_keys(dev_keys).boardloader_keys
class LegacyFirmware(firmware.LegacyFirmware):
NAME: t.ClassVar[str] = "legacy_firmware_v1"
@ -488,6 +557,11 @@ def parse_image(image: bytes) -> SignableImageProto:
except c.ConstructError:
pass
try:
return SecmonImage.parse(image)
except c.ConstructError:
pass
try:
firmware_img = firmware.core.FirmwareImage.parse(image)
if firmware_img.header.magic == firmware.core.HeaderType.BOOTLOADER: