Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SFT-3536: Validate header with foundation-firmware.
Browse files Browse the repository at this point in the history
* extmod/foundation-rust/include/foundation.h: Update bindings.
* extmod/foundation-rust/src/firmware.rs: New module.
* extmod/foundation-rust/src/lib.rs (firmware): Register module.
* ports/stm32/boards/Passport/modpassport-system.h
(mod_passport_System_validate_firmware_header): Remove procedure.
(mod_passport_System_validate_firmware_header_obj): Remove variable.
(mod_passport_System_locals_dict_table): Remove
validate_firmware_header.
* ports/stm32/boards/Passport/modpassport.c
(mod_passport_verify_update_header): New procedure.
(mod_passport_verify_update_header_obj): New variable.
(passport_module_globals_table): Add verify_update_header.
* ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py
(UpdateFirmwareFlow) <show_firmware_details>: Use verify_update_header
instead of validate_firmware_header.
* ports/stm32/boards/Passport/modules/tasks/verify_firmware_signature_task.py:
Add new task.
jeandudey committed May 15, 2024

Verified

This commit was signed with the committer’s verified signature.
jeandudey Jean-Pierre De Jesus DIAZ
1 parent 9d665a0 commit b40c4fd
Showing 11 changed files with 623 additions and 124 deletions.
128 changes: 128 additions & 0 deletions extmod/foundation-rust/include/foundation.h
Original file line number Diff line number Diff line change
@@ -18,6 +18,8 @@
#include <stdint.h>
#include <stdlib.h>

#define VERSION_LEN 8

/**
* Maximum size of an encoded Uniform Resource.
*
@@ -108,6 +110,118 @@ typedef struct UR_Decoder UR_Decoder;
*/
typedef struct UR_Encoder UR_Encoder;

/**
* The result of the firmware update verification.
*/
typedef enum {
/**
* The firmware validation succeed.
*/
FIRMWARE_RESULT_HEADER_OK,
/**
* The header format is not valid.
*/
FIRMWARE_RESULT_INVALID_HEADER,
/**
* Unknown magic number.
*/
FIRMWARE_RESULT_UNKNOWN_MAGIC,
/**
* The timestamp field is invalid.
*/
FIRMWARE_RESULT_INVALID_TIMESTAMP,
/**
* The firmware is too small.
*/
FIRMWARE_RESULT_TOO_SMALL,
/**
* The firmware is too big.
*/
FIRMWARE_RESULT_TOO_BIG,
/**
* The firmware is older than the current firmware.
*/
FIRMWARE_RESULT_TOO_OLD,
/**
* Public Key 1 is out of range.
*/
FIRMWARE_RESULT_INVALID_PUBLIC_KEY1_INDEX,
/**
* Public Key 2 is out of range.
*/
FIRMWARE_RESULT_INVALID_PUBLIC_KEY2_INDEX,
/**
* The same public key was used for the two signatures.
*/
FIRMWARE_RESULT_SAME_PUBLIC_KEY,
/**
* Signature verification succeed.
*/
FIRMWARE_RESULT_SIGNATURES_OK,
/**
* The user signed firmware is not valid.
*/
FIRMWARE_RESULT_INVALID_USER_SIGNATURE,
/**
* The first signature verification failed.
*/
FIRMWARE_RESULT_FAILED_SIGNATURE1,
/**
* The second signature verification failed.
*/
FIRMWARE_RESULT_FAILED_SIGNATURE2,
} FirmwareResult_Tag;

typedef struct {
char version[VERSION_LEN];
bool signed_by_user;
} FirmwareResult_HeaderOk_Body;

typedef struct {
uint32_t magic;
} FirmwareResult_UnknownMagic_Body;

typedef struct {
uint32_t len;
} FirmwareResult_TooSmall_Body;

typedef struct {
uint32_t len;
} FirmwareResult_TooBig_Body;

typedef struct {
uint32_t timestamp;
} FirmwareResult_TooOld_Body;

typedef struct {
uint32_t index;
} FirmwareResult_InvalidPublicKey1Index_Body;

typedef struct {
uint32_t index;
} FirmwareResult_InvalidPublicKey2Index_Body;

typedef struct {
/**
* Index of the duplicated key.
*/
uint32_t index;
} FirmwareResult_SamePublicKey_Body;

typedef struct {
FirmwareResult_Tag tag;
union {
FirmwareResult_HeaderOk_Body HEADER_OK;
FirmwareResult_UnknownMagic_Body UNKNOWN_MAGIC;
FirmwareResult_TooSmall_Body TOO_SMALL;
FirmwareResult_TooBig_Body TOO_BIG;
FirmwareResult_TooOld_Body TOO_OLD;
FirmwareResult_InvalidPublicKey1Index_Body INVALID_PUBLIC_KEY1_INDEX;
FirmwareResult_InvalidPublicKey2Index_Body INVALID_PUBLIC_KEY2_INDEX;
FirmwareResult_SamePublicKey_Body SAME_PUBLIC_KEY;
};
} FirmwareResult;

typedef struct {
UR_ErrorKind kind;
const char *message;
@@ -360,6 +474,20 @@ extern UR_Decoder UR_DECODER;

extern UR_Encoder UR_ENCODER;

/**
* Verify the header of a firmware update.
*/
void foundation_firmware_verify_update_header(const uint8_t *header,
size_t header_len,
uint32_t current_timestamp,
FirmwareResult *result);

void foundation_firmware_verify_update_signatures(const uint8_t *header,
size_t header_len,
uint32_t current_timestamp,
const uint8_t (*hash)[32],
FirmwareResult *result);

/**
* Calculate a "Schnorr" public key from the secret key.
*
199 changes: 199 additions & 0 deletions extmod/foundation-rust/src/firmware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::secp256k1::PRE_ALLOCATED_CTX;
use bitcoin_hashes::{sha256d, Hash};
use core::{ffi::c_char, slice};
use foundation_firmware::{VerifyHeaderError, VerifySignatureError};

pub const VERSION_LEN: usize = 8;

/// The result of the firmware update verification.
/// cbindgen:rename-all=ScreamingSnakeCase
/// cbindgen:prefix-with-name
#[repr(C)]
pub enum FirmwareResult {
// Header.
/// The firmware validation succeed.
HeaderOk {
version: [c_char; VERSION_LEN],
signed_by_user: bool,
},
/// The header format is not valid.
InvalidHeader,
/// Unknown magic number.
UnknownMagic { magic: u32 },
/// The timestamp field is invalid.
InvalidTimestamp,
/// The firmware is too small.
TooSmall { len: u32 },
/// The firmware is too big.
TooBig { len: u32 },
/// The firmware is older than the current firmware.
TooOld {
timestamp: u32,
// version: *const c_char,
},
/// Public Key 1 is out of range.
InvalidPublicKey1Index { index: u32 },
/// Public Key 2 is out of range.
InvalidPublicKey2Index { index: u32 },
/// The same public key was used for the two signatures.
SamePublicKey {
/// Index of the duplicated key.
index: u32,
},
// Signatures.
/// Signature verification succeed.
SignaturesOk,
/// The user signed firmware is not valid.
InvalidUserSignature,
/// The first signature verification failed.
FailedSignature1,
/// The second signature verification failed.
FailedSignature2,
}

impl From<VerifyHeaderError> for FirmwareResult {
fn from(e: VerifyHeaderError) -> Self {
use FirmwareResult::*;

match e {
VerifyHeaderError::UnknownMagic(magic) => UnknownMagic { magic },
VerifyHeaderError::InvalidTimestamp => InvalidTimestamp,
VerifyHeaderError::FirmwareTooSmall(len) => TooSmall { len },
VerifyHeaderError::FirmwareTooBig(len) => TooBig { len },
VerifyHeaderError::InvalidPublicKey1Index(index) => {
InvalidPublicKey1Index { index }
}
VerifyHeaderError::InvalidPublicKey2Index(index) => {
InvalidPublicKey2Index { index }
}
VerifyHeaderError::SamePublicKeys(index) => SamePublicKey { index },
}
}
}

impl From<VerifySignatureError> for FirmwareResult {
fn from(e: VerifySignatureError) -> Self {
use FirmwareResult::*;

match e {
VerifySignatureError::InvalidUserSignature { .. } => {
InvalidUserSignature
}
VerifySignatureError::FailedSignature1 { .. } => FailedSignature1,
VerifySignatureError::FailedSignature2 { .. } => FailedSignature2,
// No need to implement this, see comment on
// verify_update_signatures.
VerifySignatureError::MissingUserPublicKey => unimplemented!(),
}
}
}

fn verify_update_header_impl(
header: &[u8],
current_timestamp: u32,
result: &mut FirmwareResult,
) -> Option<foundation_firmware::Header> {
let header = match foundation_firmware::header(header) {
Ok((_, header)) => header,
Err(_) => {
*result = FirmwareResult::InvalidHeader;
return None;
}
};

if let Err(e) = header.verify() {
*result = FirmwareResult::from(e);
return None;
}

if header.information.timestamp < current_timestamp {
*result = FirmwareResult::TooOld {
timestamp: header.information.timestamp,
};
return None;
}

Some(header)
}

/// Verify the header of a firmware update.
#[export_name = "foundation_firmware_verify_update_header"]
pub extern "C" fn verify_update_header(
header: *const u8,
header_len: usize,
current_timestamp: u32,
result: &mut FirmwareResult,
) {
let header = unsafe { slice::from_raw_parts(header, header_len) };

match verify_update_header_impl(header, current_timestamp, result) {
Some(header) => {
let version_bytes = header.information.version.as_bytes();
let mut version = [0; VERSION_LEN];
for (i, &b) in version_bytes.iter().enumerate() {
version[i] = b as c_char;
}
version[version_bytes.len()] = b'\0' as c_char;

*result = FirmwareResult::HeaderOk {
version,
signed_by_user: header.is_signed_by_user(),
};
}
// Verification failed.
None => (),
}
}

#[export_name = "foundation_firmware_verify_update_signatures"]
pub extern "C" fn verify_update_signatures(
header: *const u8,
header_len: usize,
current_timestamp: u32,
hash: &[u8; 32],
result: &mut FirmwareResult,
) {
let header = unsafe { slice::from_raw_parts(header, header_len) };
let firmware_hash = sha256d::Hash::from_slice(hash)
.expect("hash should be of correct length");

let header =
match verify_update_header_impl(header, current_timestamp, result) {
Some(header) => header,
None => return,
};

match foundation_firmware::verify_signature(
&PRE_ALLOCATED_CTX,
&header,
&firmware_hash,
None,
) {
Ok(()) => {
*result = FirmwareResult::SignaturesOk;
}
// The code calling this function must make sure that there's an user
// public key provided to us before verifying signatures.
//
// When verifying signatures is because we are committed to the
// update, i.e. the user has accepted to do it, so we must have
// presented an error if there was not an user public key earlier.
Err(VerifySignatureError::MissingUserPublicKey) => {
unreachable!("we always provide a user public key")
}
Err(e) => *result = FirmwareResult::from(e),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn sanity_test() {
assert_eq!(VERSION_LEN, foundation_firmware::VERSION_LEN);
}
}
1 change: 1 addition & 0 deletions extmod/foundation-rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
#[cfg(target_arch = "arm")]
use cortex_m as _;

pub mod firmware;
pub mod secp256k1;
pub mod ur;

2 changes: 1 addition & 1 deletion extmod/foundation-rust/src/secp256k1.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ use secp256k1::{
static mut PRE_ALLOCATED_CTX_BUF: [AlignedType; 20] = [AlignedType::ZERO; 20];

/// cbindgen:ignore
static PRE_ALLOCATED_CTX: Lazy<Secp256k1<AllPreallocated<'static>>> =
pub static PRE_ALLOCATED_CTX: Lazy<Secp256k1<AllPreallocated<'static>>> =
Lazy::new(|| {
// SAFETY:
//
3 changes: 2 additions & 1 deletion ports/stm32/boards/Passport/manifest.py
Original file line number Diff line number Diff line change
@@ -285,7 +285,8 @@
'tasks/sign_psbt_task.py',
'tasks/sign_text_file_task.py',
'tasks/validate_psbt_task.py',
'tasks/verify_backup_task.py'))
'tasks/verify_backup_task.py',
'tasks/verify_firmware_signature_task.py'))

# Translations
freeze('$(MPY_DIR)/ports/stm32/boards/Passport/modules',
116 changes: 3 additions & 113 deletions ports/stm32/boards/Passport/modpassport-system.h
Original file line number Diff line number Diff line change
@@ -23,6 +23,8 @@
#include "adc.h"
#include "utils.h"

#include "foundation.h"

#define SYSTEM_DEBUG 0
#define SECRETS_FLASH_START (0x81C0000)
#define SECRETS_FLASH_SIZE (0x20000)
@@ -366,117 +368,6 @@ STATIC mp_obj_t mod_passport_System_busy_wait(mp_obj_t self, mp_obj_t _delay_ms)
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_passport_System_busy_wait_obj, mod_passport_System_busy_wait);

// Simple header verification
STATIC bool verify_header(passport_firmware_header_t* hdr) {
#ifdef SCREEN_MODE_MONO
if (hdr->info.magic != FW_HEADER_MAGIC) return false;
#elif SCREEN_MODE_COLOR
if (hdr->info.magic != FW_HEADER_MAGIC_COLOR) return false;
#endif
if (hdr->info.timestamp == 0) return false;
if (hdr->info.fwversion[0] == 0x0) return false;
if (hdr->info.fwlength < FW_HEADER_SIZE) return false;
if (hdr->info.fwlength > FW_MAX_SIZE) return false;

// Make sure pubkey indices are in range and are not the same pubkey
if ((hdr->signature.pubkey1 != FW_USER_KEY) && (hdr->signature.pubkey1 >= FW_MAX_PUB_KEYS)) return false;
if (hdr->signature.pubkey2 >= FW_MAX_PUB_KEYS) return false;
if (hdr->signature.pubkey1 == hdr->signature.pubkey2) return false;

return true;
}

/// def validate_firmware_header(self, header: bytearray) -> None:
/// '''
/// Validate the given firmware header bytes as a potential candidate to be
/// installed.
/// '''
STATIC mp_obj_t mod_passport_System_validate_firmware_header(mp_obj_t self, mp_obj_t header) {
mp_buffer_info_t header_info;
mp_get_buffer_raise(header, &header_info, MP_BUFFER_READ);

// Existing header
passport_firmware_header_t* fwhdr = (passport_firmware_header_t*)FW_HDR;

// New header
passport_firmware_header_t* new_fwhdr = (passport_firmware_header_t*)header_info.buf;

mp_obj_t tuple[4];

bool is_valid = verify_header(header_info.buf);

if (is_valid) {
// Build the current board hash so we can get the minimum firmware timestamp
uint8_t current_board_hash[HASH_LEN] = {0};
get_current_board_hash(current_board_hash);

uint32_t firmware_timestamp = se_get_firmware_timestamp(current_board_hash);

// Ensure they are not trying to install an older version of firmware, but allow
// a reinstall of the same version. Also allow installation of user firmware regardless of
// timestamp and then allow installing a Foundation-signed build.
if ((new_fwhdr->signature.pubkey1 != FW_USER_KEY) && (new_fwhdr->info.timestamp < firmware_timestamp)) {
tuple[0] = mp_const_false;
tuple[1] = mp_obj_new_str_copy(&mp_type_str, (const uint8_t*)new_fwhdr->info.fwversion,
strlen((const char*)new_fwhdr->info.fwversion));

// Include an error string
vstr_t vstr;
vstr_init(&vstr, 120);

char* msg = "The selected firmware is older than the currently installed firmware.";
vstr_add_strn(&vstr, (const char*)msg, strlen(msg));

vstr_add_strn(&vstr, (const char*)fwhdr->info.fwdate, strlen((const char*)new_fwhdr->info.fwdate));

msg = "\n\nSelected Version:\n ";
vstr_add_strn(&vstr, (const char*)msg, strlen(msg));

vstr_add_strn(&vstr, (const char*)new_fwhdr->info.fwdate, strlen((const char*)new_fwhdr->info.fwdate));
tuple[2] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr);

// Is this user-signed firmware?
tuple[3] = mp_const_false;

return mp_obj_new_tuple(4, tuple);
}
} else {
// Invalid header
tuple[0] = mp_const_false;
tuple[1] = mp_obj_new_str_copy(&mp_type_str, (const uint8_t*)new_fwhdr->info.fwversion,
strlen((const char*)new_fwhdr->info.fwversion));

// Include an error string
vstr_t vstr;
vstr_init(&vstr, 80);
char* msg = "The selected firmware header is invalid and cannot be installed.";
vstr_add_strn(&vstr, (const char*)msg, strlen(msg));
tuple[2] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr);

// No header = no user signed firmware
tuple[3] = mp_const_false;

return mp_obj_new_tuple(4, tuple);
}

// is_valid
tuple[0] = mp_const_true;

// Firmware version
tuple[1] = mp_obj_new_str_copy(&mp_type_str, (const uint8_t*)new_fwhdr->info.fwversion,
strlen((const char*)new_fwhdr->info.fwversion));

// No error message
tuple[2] = mp_const_none;

// Is this user-signed firmware?
tuple[3] = (new_fwhdr->signature.pubkey1 == FW_USER_KEY) ? mp_const_true : mp_const_false;

return mp_obj_new_tuple(4, tuple);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_passport_System_validate_firmware_header_obj,
mod_passport_System_validate_firmware_header);

/// def mod_passport_System_enable_lv_refresh(self, Int) -> None
/// """
/// Enable or disable the lv refresh.
@@ -506,7 +397,6 @@ STATIC const mp_rom_map_elem_t mod_passport_System_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR_get_screen_brightness), MP_ROM_PTR(&mod_passport_System_get_screen_brightness_obj)},
{MP_ROM_QSTR(MP_QSTR_set_screen_brightness), MP_ROM_PTR(&mod_passport_System_set_screen_brightness_obj)},
{MP_ROM_QSTR(MP_QSTR_busy_wait), MP_ROM_PTR(&mod_passport_System_busy_wait_obj)},
{MP_ROM_QSTR(MP_QSTR_validate_firmware_header), MP_ROM_PTR(&mod_passport_System_validate_firmware_header_obj)},
{MP_ROM_QSTR(MP_QSTR_enable_lv_refresh), MP_ROM_PTR(&mod_passport_System_enable_lv_refresh_obj)},
};
STATIC MP_DEFINE_CONST_DICT(mod_passport_System_locals_dict, mod_passport_System_locals_dict_table);
@@ -546,4 +436,4 @@ STATIC void _hmac_sha256(uint8_t* key, uint32_t key_len, uint8_t* msg, uint32_t
sha256_update(&ctx, o_key_pad, SHA256_BLOCK_LENGTH);
sha256_update(&ctx, hmac, SHA256_DIGEST_LENGTH);
sha256_final(&ctx, hmac);
}
}
180 changes: 180 additions & 0 deletions ports/stm32/boards/Passport/modpassport.c
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@

/// package: passport

STATIC MP_DEFINE_EXCEPTION(InvalidFirmwareUpdate, Exception);

/// def supply_chain_challenge(self, challenge: bytearray, response: bytearray) -> boolean:
/// """
/// Perform the supply chain challenge (HMAC)
@@ -61,13 +63,191 @@ STATIC mp_obj_t mod_passport_verify_supply_chain_server_signature(mp_obj_t hash_
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_passport_verify_supply_chain_server_signature_obj,
mod_passport_verify_supply_chain_server_signature);

/// def verify_update_header(self, header: bytearray) -> None:
/// '''
/// Verify the given firmware header bytes as a potential candidate to be
/// installed.
/// '''
STATIC mp_obj_t mod_passport_verify_update_header(mp_obj_t header) {
mp_obj_t tuple[2];

mp_buffer_info_t header_info;
mp_get_buffer_raise(header, &header_info, MP_BUFFER_READ);

// Build the current board hash so we can get the minimum firmware
// timestamp
uint8_t current_board_hash[HASH_LEN] = {0};
get_current_board_hash(current_board_hash);

uint32_t firmware_timestamp = se_get_firmware_timestamp(current_board_hash);

FirmwareResult result = {0};
foundation_firmware_verify_update_header(header_info.buf,
header_info.len,
firmware_timestamp,
&result);

switch (result.tag) {
case FIRMWARE_RESULT_HEADER_OK:
tuple[0] = mp_obj_new_str_copy(&mp_type_str,
(const uint8_t*)result.HEADER_OK.version,
strlen((const char*)result.HEADER_OK.version));
tuple[1] = result.HEADER_OK.signed_by_user ? mp_const_true : mp_const_false;
return mp_obj_new_tuple(2, tuple);
case FIRMWARE_RESULT_INVALID_HEADER:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Invalid firmware header"));
case FIRMWARE_RESULT_UNKNOWN_MAGIC:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Unknown firmware magic bytes"));
break;
case FIRMWARE_RESULT_INVALID_TIMESTAMP:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Invalid firmware timestamp"));
break;
case FIRMWARE_RESULT_TOO_SMALL:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Firmware size is too small: %"PRIu32" bytes."),
result.TOO_SMALL.len);
break;
case FIRMWARE_RESULT_TOO_BIG:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Firmware size is too big: %"PRIu32" bytes."),
result.TOO_BIG.len);
break;
case FIRMWARE_RESULT_TOO_OLD:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Firmware is older than current, timestamp is: %"PRIu32"."),
result.TOO_OLD.timestamp);
break;
case FIRMWARE_RESULT_INVALID_PUBLIC_KEY1_INDEX:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Public Key is out of range (%"PRIu32")."),
result.INVALID_PUBLIC_KEY1_INDEX.index);
break;
case FIRMWARE_RESULT_INVALID_PUBLIC_KEY2_INDEX:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Public Key is out of range (%"PRIu32")."),
result.INVALID_PUBLIC_KEY2_INDEX.index);
break;
case FIRMWARE_RESULT_SAME_PUBLIC_KEY:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Same Public Key was used for both signatures (%"PRIu32")."),
result.SAME_PUBLIC_KEY.index);
break;
default:
break;
}

mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Unhandled case in firmware verification"));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_passport_verify_update_header_obj,
mod_passport_verify_update_header);

STATIC mp_obj_t mod_passport_verify_update_signatures(mp_obj_t header, mp_obj_t validation_hash) {
mp_buffer_info_t header_info;
mp_get_buffer_raise(header, &header_info, MP_BUFFER_READ);

mp_buffer_info_t validation_hash_info;
mp_get_buffer_raise(validation_hash, &validation_hash_info, MP_BUFFER_READ);

// Build the current board hash so we can get the minimum firmware
// timestamp
uint8_t current_board_hash[HASH_LEN] = {0};
get_current_board_hash(current_board_hash);

uint32_t firmware_timestamp = se_get_firmware_timestamp(current_board_hash);

// TODO: This one causes text to overflow.
FirmwareResult result = {0};
foundation_firmware_verify_update_signatures(header_info.buf,
header_info.len,
firmware_timestamp,
validation_hash_info.buf,
&result);

// Signature verification also validates the header again, so, we have
// to handle the errors again.
//
// TODO: This should be factored out.
switch (result.tag) {
case FIRMWARE_RESULT_SIGNATURES_OK:
return mp_const_true;
case FIRMWARE_RESULT_INVALID_HEADER:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Invalid firmware header"));
case FIRMWARE_RESULT_UNKNOWN_MAGIC:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Unknown firmware magic bytes"));
break;
case FIRMWARE_RESULT_INVALID_TIMESTAMP:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Invalid firmware timestamp"));
break;
case FIRMWARE_RESULT_TOO_SMALL:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Firmware size is too small: %"PRIu32" bytes."),
result.TOO_SMALL.len);
break;
case FIRMWARE_RESULT_TOO_BIG:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Firmware size is too big: %"PRIu32" bytes."),
result.TOO_BIG.len);
break;
case FIRMWARE_RESULT_TOO_OLD:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Firmware is older than current, timestamp is: %"PRIu32"."),
result.TOO_OLD.timestamp);
break;
case FIRMWARE_RESULT_INVALID_PUBLIC_KEY1_INDEX:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Public Key is out of range (%"PRIu32")."),
result.INVALID_PUBLIC_KEY1_INDEX.index);
break;
case FIRMWARE_RESULT_INVALID_PUBLIC_KEY2_INDEX:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Public Key is out of range (%"PRIu32")."),
result.INVALID_PUBLIC_KEY2_INDEX.index);
break;
case FIRMWARE_RESULT_SAME_PUBLIC_KEY:
mp_raise_msg_varg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Same Public Key was used for both signatures (%"PRIu32")."),
result.SAME_PUBLIC_KEY.index);
break;
case FIRMWARE_RESULT_INVALID_USER_SIGNATURE:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("User signature verification failed"));
break;
case FIRMWARE_RESULT_FAILED_SIGNATURE1:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("First signature verification failed"));
case FIRMWARE_RESULT_FAILED_SIGNATURE2:
mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Second signature verification failed"));
default:
break;
}

mp_raise_msg(&mp_type_InvalidFirmwareUpdate,
MP_ERROR_TEXT("Unhandled case in firmware signatures verification"));
return mp_const_false;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_passport_verify_update_signatures_obj,
mod_passport_verify_update_signatures);

STATIC const mp_rom_map_elem_t passport_module_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_passport)},
{MP_ROM_QSTR(MP_QSTR_InvalidFirmwareUpdate), MP_ROM_PTR(&mp_type_InvalidFirmwareUpdate)},
{MP_ROM_QSTR(MP_QSTR_camera), MP_ROM_PTR(&mod_passport_camera_module)},
{MP_ROM_QSTR(MP_QSTR_IS_SIMULATOR), MP_ROM_FALSE},
{MP_ROM_QSTR(MP_QSTR_supply_chain_challenge), MP_ROM_PTR(&mod_passport_supply_chain_challenge_obj)},
{MP_ROM_QSTR(MP_QSTR_verify_supply_chain_server_signature),
MP_ROM_PTR(&mod_passport_verify_supply_chain_server_signature_obj)},
{MP_ROM_QSTR(MP_QSTR_verify_update_header),
MP_ROM_PTR(&mod_passport_verify_update_header_obj)},
{MP_ROM_QSTR(MP_QSTR_verify_update_signatures),
MP_ROM_PTR(&mod_passport_verify_update_signatures_obj)},
#ifdef SCREEN_MODE_COLOR
{MP_ROM_QSTR(MP_QSTR_IS_COLOR), MP_ROM_TRUE},
#else
1 change: 1 addition & 0 deletions ports/stm32/boards/Passport/modules/constants.py
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
FW_MAX_SIZE = (1792 * 1024) - 256
FW_START = 0
FW_HEADER_SIZE = 2048
FW_HEADER_INFORMATION_SIZE = 34 # sizeof(fw_info_t)
FW_ACTUAL_HEADER_SIZE = 170 # passport_firmware_header_t uses this many bytes

# PSBT signing (uses same memory as firmware updates)
42 changes: 33 additions & 9 deletions ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py
Original file line number Diff line number Diff line change
@@ -8,10 +8,11 @@
from constants import FW_HEADER_SIZE, FW_MAX_SIZE
import machine
from pages import ErrorPage, ProgressPage, QuestionPage, SuccessPage, InsertMicroSDPage, InfoPage
from tasks import copy_firmware_to_spi_flash_task
from tasks import copy_firmware_to_spi_flash_task, verify_firmware_signature_task
from flows import Flow, FilePickerFlow
from utils import read_user_firmware_pubkey, is_all_zero, start_task
from errors import Error
import passport


class UpdateFirmwareFlow(Flow):
@@ -77,13 +78,11 @@ async def show_firmware_details(self):
self.set_result(False)
return

# Validate the header
is_valid, version, error_msg, is_user_signed = common.system.validate_firmware_header(header)
# print('is_valid={}, version={}, error_msg={}, is_user_signed={}'.format(
# is_valid, version, error_msg, is_user_signed))
self.version = version
if not is_valid:
await ErrorPage(text='Firmware header is invalid.\n\n{}'.format(error_msg)).show()
try:
version, is_user_signed = passport.verify_update_header(header)
self.version = version
except passport.InvalidFirmwareUpdate as e:
await ErrorPage(text='Firmware update is invalid.\n\n{}'.format(str(e))).show()
self.set_result(False)
return

@@ -100,10 +99,35 @@ async def show_firmware_details(self):

result = await QuestionPage(text='Install this firmware update?\n\nVersion:\n{}'.format(version)).show()
if result:
self.goto(self.copy_to_flash)
self.goto(self.verify_firmware_signature)
else:
self.set_result(False)

async def verify_firmware_signature(self):
from common import ui

self.progress_page = ProgressPage(text='Veryfing Signatures', left_micron=None, right_micron=None)

self.verify_task = start_task(verify_firmware_signature_task(
self.update_file_path, self.size, self.progress_page.set_progress, self.on_done))

prev_top_level = ui.set_is_top_level(False)
result = await self.progress_page.show()
if result:
ui.set_is_top_level(prev_top_level)
self.goto(self.copy_to_flash)
elif self.error is Error.MICROSD_CARD_MISSING:
ui.set_is_top_level(prev_top_level)
result = await InsertMicroSDPage().show()
if not result:
self.back()
else:
ui.set_is_top_level(prev_top_level)
await ErrorPage("Failed to copy new firmware:\n{}".format(self.error_message)).show()
await InfoPage("Passport will continue using the current firmware").show()
self.set_result(False)


async def copy_to_flash(self):
from common import settings, system, ui

1 change: 1 addition & 0 deletions ports/stm32/boards/Passport/modules/tasks/__init__.py
Original file line number Diff line number Diff line change
@@ -53,4 +53,5 @@
from .sign_psbt_task import sign_psbt_task
from .sign_text_file_task import sign_text_file_task
from .validate_psbt_task import validate_psbt_task
from .verify_firmware_signature_task import verify_firmware_signature_task
from .verify_backup_task import verify_backup_task
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Task to verify the signatures of a firmware update before copying to
# SPI flash.
#
# SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. <hello@foundationdevices.com>
# SPDX-License-Identifier: GPL-3.0-or-later

import passport
import trezorcrypto
import foundation
from uasyncio import sleep_ms
from ubinascii import hexlify as b2a_hex
from constants import FW_HEADER_SIZE, FW_HEADER_INFORMATION_SIZE
from files import CardSlot, CardMissingError
from errors import Error

async def verify_firmware_signature_task(file_path, size, on_progress, on_done):
header = None
s = trezorcrypto.sha256()

try:
with CardSlot() as card:
with open(file_path, 'rb') as fp:
# This is assumed to have been validated before, TOCTOU
# attacks are not an issue here since if the information data
# changes so does the validation hash making the signature
# verification to fail.
header = fp.read(FW_HEADER_SIZE)
s.update(header[:FW_HEADER_INFORMATION_SIZE])

buf = bytearray(1024)
pos = FW_HEADER_SIZE
update_display = 0
while pos < size:
# Update every 50 reads.
if update_display % 50 == 0:
percent = int((pos / size) * 100)
on_progress(percent)

# Allow UI to update progress.
await sleep_ms(1)

here = fp.readinto(buf)
s.update(buf[:here])
pos += here

update_display += 1
except CardMissingError:
await on_done(Error.MICROSD_CARD_MISSING, None)
return
except Exception as e:
error_message = "Firmware update error: {}, Info: {}".format(e.__class__.__name__,
e.args[0] if len(e.args) == 1 else e.args)
await on_done(Error.FIRMWARE_UPDATE_FAILED, error_message)
return

single_sha = s.digest()
double_sha = bytearray(32)
foundation.sha256(single_sha, double_sha)

print('verifying signatures')
try:
print('verifying signatures')
passport.verify_update_signatures(header, double_sha)
except passport.InvalidFirmwareUpdate as e:
await on_done(Error.FIRMWARE_UPDATE_FAILED, "Invalid firmware signatures")
return
except Exception as e:
error_message = "Firmware update error: {}, Info: {}".format(e.__class__.__name__,
e.args[0] if len(e.args) == 1 else e.args)
await on_done(Error.FIRMWARE_UPDATE_FAILED, error_message)
return

print('we re done my friend')
await on_done(None, None)

0 comments on commit b40c4fd

Please sign in to comment.