Skip to content

Commit

Permalink
memory: persist attestation bootloader hash
Browse files Browse the repository at this point in the history
And use it in the attestation check.

`bootloader_hash_set` is added just in case the memory is unexpectedly
not `0xFF` everywhere (the default value for unused/erased memory).
  • Loading branch information
benma committed Mar 19, 2024
1 parent 1b1dfca commit bacbf50
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ add_custom_target(rust-bindgen
--allowlist-function memory_is_seeded
--allowlist-function memory_is_mnemonic_passphrase_enabled
--allowlist-function memory_get_attestation_pubkey_and_certificate
--allowlist-function memory_get_attestation_bootloader_hash
--allowlist-function memory_bootloader_hash
--allowlist-function memory_get_noise_static_private_key
--allowlist-function memory_check_noise_remote_static_pubkey
Expand Down
4 changes: 4 additions & 0 deletions src/factorysetup.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ static void _api_msg(const Packet* in_packet, Packet* out_packet, const size_t m
switch (input[0]) {
case OP_BOOTLOADER_HASH:
memory_bootloader_hash(output + 2);
// This is the hash that will be used for the attestation, persist for later use.
if (!memory_set_attestation_bootloader_hash()) {
screen_print_debug("setting attestation bootloader hash failed", 0);
}
out_len = 2 + 32;
break;
case OP_GENKEY: {
Expand Down
41 changes: 41 additions & 0 deletions src/memory/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ typedef struct __attribute__((__packed__)) {
uint8_t certificate[64]; // SECP256k1 signature (R, S)
// Identifier of the root pubkey whose privkey generated the certificate
uint8_t root_pubkey_identifier[32];
// Hash of bootloader used in the attestation sighash.
// If not set, the actual bootloader area is hashed.
secbool_u8 bootloader_hash_set;
uint8_t reserved[3];
uint8_t bootloader_hash[32];
} attestation_t;

// CHUNK_0_PERMANENT: Written during factory installation, never changed.
Expand Down Expand Up @@ -557,6 +562,38 @@ static bool _is_attestation_setup_done(void)
return !MEMEQ(chunk.fields.attestation.certificate, empty, 64);
}

bool memory_set_attestation_bootloader_hash(void)
{
chunk_0_t chunk = {0};
CLEANUP_CHUNK(chunk);
_read_chunk(CHUNK_0_PERMANENT, chunk_bytes);
uint8_t empty[32];
memset(empty, 0xff, sizeof(empty));
if (chunk.fields.attestation.bootloader_hash_set != sectrue_u8 ||
MEMEQ(chunk.fields.attestation.bootloader_hash, empty, sizeof(empty))) {
chunk.fields.attestation.bootloader_hash_set = sectrue_u8;
memory_bootloader_hash(chunk.fields.attestation.bootloader_hash);
return _write_chunk(CHUNK_0_PERMANENT, chunk.bytes);
}

return true;
}

void memory_get_attestation_bootloader_hash(uint8_t* hash_out)
{
chunk_0_t chunk = {0};
CLEANUP_CHUNK(chunk);
_read_chunk(CHUNK_0_PERMANENT, chunk_bytes);
uint8_t empty[32];
memset(empty, 0xff, sizeof(empty));
if (chunk.fields.attestation.bootloader_hash_set != sectrue_u8 ||
MEMEQ(chunk.fields.attestation.bootloader_hash, empty, sizeof(empty))) {
memory_bootloader_hash(hash_out);
return;
}
memcpy(hash_out, chunk.fields.attestation.bootloader_hash, 32);
}

bool memory_set_attestation_device_pubkey(const uint8_t* attestation_device_pubkey)
{
chunk_0_t chunk = {0};
Expand Down Expand Up @@ -610,9 +647,13 @@ bool memory_get_attestation_pubkey_and_certificate(

void memory_bootloader_hash(uint8_t* hash_out)
{
#ifdef TESTING
memory_bootloader_hash_mock(hash_out);
#else
uint8_t* bootloader = FLASH_BOOT_START;
size_t len = FLASH_BOOT_LEN - 32; // 32 bytes are random
rust_sha256(bootloader, len, hash_out);
#endif
}

bool memory_bootloader_set_flags(auto_enter_t auto_enter, upside_down_t upside_down)
Expand Down
11 changes: 11 additions & 0 deletions src/memory/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ void memory_get_io_protection_key(uint8_t* key_out);
void memory_get_authorization_key(uint8_t* key_out);
void memory_get_encryption_key(uint8_t* key_out);

/**
* Persists the current bootloader hash that, which is part of the attestation sighash.
*/
USE_RESULT bool memory_set_attestation_bootloader_hash(void);

/**
* Retreives the bootloader hash that is part of the attestation sighash.
* @param[out] hash_out must be 32 bytes and will contain the result.
*/
void memory_get_attestation_bootloader_hash(uint8_t* hash_out);

/**
* Persists the given attestation device pubkey.
* @param[in] attestation_device_pubkey P256 NIST ECC pubkey (X and Y coordinates).
Expand Down
2 changes: 1 addition & 1 deletion src/rust/bitbox02-rust/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn perform(host_challenge: [u8; 32]) -> Result<Data, ()> {
&mut result.root_pubkey_identifier,
)?;
let hash: [u8; 32] = Sha256::digest(host_challenge).into();
bitbox02::memory::bootloader_hash(&mut result.bootloader_hash);
result.bootloader_hash = bitbox02::memory::get_attestation_bootloader_hash();
bitbox02::securechip::attestation_sign(&hash, &mut result.challenge_signature)?;
Ok(result)
}
25 changes: 19 additions & 6 deletions src/rust/bitbox02/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ pub fn is_mnemonic_passphrase_enabled() -> bool {
unsafe { bitbox02_sys::memory_is_mnemonic_passphrase_enabled() }
}

pub fn get_attestation_bootloader_hash() -> [u8; 32] {
let mut hash = [0u8; 32];
unsafe {
bitbox02_sys::memory_get_attestation_bootloader_hash(hash.as_mut_ptr());
}
hash
}

pub fn get_attestation_pubkey_and_certificate(
device_pubkey: &mut [u8; 64],
certificate: &mut [u8; 64],
Expand All @@ -82,12 +90,6 @@ pub fn get_attestation_pubkey_and_certificate(
}
}

pub fn bootloader_hash(out: &mut [u8; 32]) {
unsafe {
bitbox02_sys::memory_bootloader_hash(out.as_mut_ptr());
}
}

pub fn get_noise_static_private_key() -> Result<zeroize::Zeroizing<[u8; 32]>, ()> {
let mut out = zeroize::Zeroizing::new([0u8; 32]);
match unsafe { bitbox02_sys::memory_get_noise_static_private_key(out.as_mut_ptr()) } {
Expand Down Expand Up @@ -157,3 +159,14 @@ pub fn multisig_get_by_hash(hash: &[u8]) -> Option<String> {
false => None,
}
}

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

#[test]
fn test_get_attestation_bootloader_hash() {
let expected: [u8; 32] = *b"\x71\x3d\xf0\xd5\x8c\x71\x7d\x40\x31\x78\x7c\xdc\x8f\xa3\x5b\x90\x25\x82\xbe\x6a\xb6\xa2\x2e\x09\xde\x44\x77\xd3\x0e\x22\x30\xfc";
assert_eq!(get_attestation_bootloader_hash(), expected);
}
}
23 changes: 17 additions & 6 deletions test/simulator/framework/mock_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,35 @@ static uint8_t* _get_memory(uint32_t base)
bool memory_write_to_address_mock(uint32_t base, uint32_t addr, const uint8_t* chunk)
{
if (chunk == NULL) {
memset(_get_memory(base) + addr, 0xff, CHUNK_SIZE);
memset(_get_memory(base) + addr, 0xff, (size_t)CHUNK_SIZE);
} else {
memcpy(_get_memory(base) + addr, chunk, CHUNK_SIZE);
memcpy(_get_memory(base) + addr, chunk, (size_t)CHUNK_SIZE);
}
return true;
}

bool memory_write_chunk_mock(uint32_t chunk_num, const uint8_t* chunk)
{
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * CHUNK_SIZE, chunk);
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * (size_t)CHUNK_SIZE, chunk);
}

void memory_read_chunk_mock(uint32_t chunk_num, uint8_t* chunk_out)
{
memcpy(chunk_out, _memory_app_data + chunk_num * CHUNK_SIZE, CHUNK_SIZE);
memcpy(chunk_out, _memory_app_data + chunk_num * (size_t)CHUNK_SIZE, (size_t)CHUNK_SIZE);
}

void memory_read_shared_bootdata_mock(uint8_t* chunk_out)
{
memcpy(chunk_out, _memory_shared_data, FLASH_SHARED_DATA_LEN);
}
memcpy(chunk_out, _memory_shared_data, (size_t)FLASH_SHARED_DATA_LEN);
}

// Arbitrary value.
static uint8_t _bootloader_hash[32] =
"\x71\x3d\xf0\xd5\x8c\x71\x7d\x40\x31\x78\x7c\xdc\x8f\xa3\x5b\x90\x25\x82\xbe\x6a\xb6\xa2\x2e"
"\x09\xde\x44\x77\xd3\x0e\x22\x30\xfc";

void memory_bootloader_hash_mock(uint8_t* hash_out)
{
// NOLINTNEXTLINE(bugprone-not-null-terminated-result)
memcpy(hash_out, _bootloader_hash, 32);
}
2 changes: 2 additions & 0 deletions test/unit-test/framework/includes/mock_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ void memory_read_chunk_mock(uint32_t chunk_num, uint8_t* chunk_out);
// Size: `FLASH_SHARED_DATA_LEN`.
void memory_read_shared_bootdata_mock(uint8_t* chunk_out);
void mock_memory_set_salt_root(const uint8_t* salt_root);
void memory_bootloader_hash_mock(uint8_t* hash_out);
void memory_set_bootloader_hash_mock(const uint8_t* mock_hash);
#endif
26 changes: 21 additions & 5 deletions test/unit-test/framework/mock_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,26 @@ static uint8_t* _get_memory(uint32_t base)
bool memory_write_to_address_mock(uint32_t base, uint32_t addr, const uint8_t* chunk)
{
if (chunk == NULL) {
memset(_get_memory(base) + addr, 0xff, CHUNK_SIZE);
memset(_get_memory(base) + addr, 0xff, (size_t)CHUNK_SIZE);
} else {
memcpy(_get_memory(base) + addr, chunk, CHUNK_SIZE);
memcpy(_get_memory(base) + addr, chunk, (size_t)CHUNK_SIZE);
}
return true;
}

bool memory_write_chunk_mock(uint32_t chunk_num, const uint8_t* chunk)
{
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * CHUNK_SIZE, chunk);
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * (size_t)CHUNK_SIZE, chunk);
}

void memory_read_chunk_mock(uint32_t chunk_num, uint8_t* chunk_out)
{
memcpy(chunk_out, _memory_app_data + chunk_num * CHUNK_SIZE, CHUNK_SIZE);
memcpy(chunk_out, _memory_app_data + chunk_num * (size_t)CHUNK_SIZE, (size_t)CHUNK_SIZE);
}

void memory_read_shared_bootdata_mock(uint8_t* chunk_out)
{
memcpy(chunk_out, _memory_shared_data, FLASH_SHARED_DATA_LEN);
memcpy(chunk_out, _memory_shared_data, (size_t)FLASH_SHARED_DATA_LEN);
}

bool __wrap_memory_is_initialized(void)
Expand Down Expand Up @@ -132,3 +132,19 @@ bool __wrap_memory_get_salt_root(uint8_t* salt_root_out)
memcpy(salt_root_out, _salt_root, 32);
return true;
}

// Arbitrary value.
static uint8_t _bootloader_hash[32] =
"\x71\x3d\xf0\xd5\x8c\x71\x7d\x40\x31\x78\x7c\xdc\x8f\xa3\x5b\x90\x25\x82\xbe\x6a\xb6\xa2\x2e"
"\x09\xde\x44\x77\xd3\x0e\x22\x30\xfc";

void memory_bootloader_hash_mock(uint8_t* hash_out)
{
memcpy(hash_out, _bootloader_hash, 32);
}

void memory_set_bootloader_hash_mock(const uint8_t* mock_hash)
{
// NOLINTNEXTLINE(bugprone-not-null-terminated-result)
memcpy(_bootloader_hash, mock_hash, sizeof(_bootloader_hash));
}
31 changes: 31 additions & 0 deletions test/unit-test/test_memory_functional.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,36 @@ static void _test_functional(void** state)
assert_memory_equal(encryption_key, expected_encryption_key, sizeof(encryption_key));
}

static void _test_attestation_bootloader_hash(void** state)
{
const uint8_t mock1[32] =
"\x03\x22\xb3\x19\x1a\xab\x5b\xc4\x15\xc5\xba\xfa\xc5\x33\x34\x45\x17\x5b\xe2\xfa\xa8\x33"
"\x3a\xc3\xab\xee\x4c\xd1\x7e\x49\x08\x2a";
memory_set_bootloader_hash_mock(mock1);
uint8_t hash[32];
memory_bootloader_hash(hash);
memory_get_attestation_bootloader_hash(hash);
assert_memory_equal(hash, mock1, sizeof(hash));

assert_true(memory_set_attestation_bootloader_hash());
memset(hash, 0x00, sizeof(hash));
memory_get_attestation_bootloader_hash(hash);
assert_memory_equal(hash, mock1, sizeof(hash));

const uint8_t mock2[32] =
"\x6c\xad\x6a\xbc\x3f\xd4\x47\xa5\x8d\x7a\x26\x2d\x76\x06\xa0\x40\xe4\x9e\x82\xb0\x06\x48"
"\x62\x36\x25\x88\x3e\x9f\xc0\xfa\xa8\xad";
memory_set_bootloader_hash_mock(mock2);

memset(hash, 0x00, sizeof(hash));
memory_bootloader_hash(hash);
assert_memory_equal(hash, mock2, sizeof(hash));

memset(hash, 0x00, sizeof(hash));
memory_get_attestation_bootloader_hash(hash);
assert_memory_equal(hash, mock1, sizeof(hash));
}

int main(void)
{
const struct CMUnitTest tests[] = {
Expand All @@ -222,6 +252,7 @@ int main(void)
cmocka_unit_test(_test_memory_multisig_full),
cmocka_unit_test(_test_memory_attestation),
cmocka_unit_test(_test_functional),
cmocka_unit_test(_test_attestation_bootloader_hash),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

0 comments on commit bacbf50

Please sign in to comment.