Skip to content

Commit

Permalink
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.
  • Loading branch information
jeandudey committed May 23, 2024
1 parent f8c79ca commit 0a630b6
Show file tree
Hide file tree
Showing 11 changed files with 641 additions and 124 deletions.
129 changes: 129 additions & 0 deletions extmod/foundation-rust/include/foundation.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <stdint.h>
#include <stdlib.h>

#define VERSION_LEN 8

/**
* Maximum size of an encoded Uniform Resource.
*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -360,6 +474,21 @@ 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],
const uint8_t (*user_public_key)[64],
FirmwareResult *result);

/**
* Calculate a "Schnorr" public key from the secret key.
*
Expand Down
212 changes: 212 additions & 0 deletions extmod/foundation-rust/src/firmware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <hello@foundationdevices.com>
// 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};
use secp256k1::PublicKey;

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],
user_public_key: Option<&[u8; 64]>,
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 user_public_key = user_public_key
.map(|v| {
let mut buf = [0; 65];
buf[0] = 0x04;
(&mut buf[1..]).copy_from_slice(v);
buf
})
.map(|v| {
PublicKey::from_slice(&v).expect("user public key should be valid")
});

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,
user_public_key.as_ref(),
) {
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
Expand Up @@ -7,6 +7,7 @@
#[cfg(target_arch = "arm")]
use cortex_m as _;

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

Expand Down
2 changes: 1 addition & 1 deletion extmod/foundation-rust/src/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//
Expand Down
3 changes: 2 additions & 1 deletion ports/stm32/boards/Passport/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 0a630b6

Please sign in to comment.