Skip to content

Commit

Permalink
Merge remote-tracking branch 'benma/hash'
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Mar 25, 2024
2 parents 128c1f2 + d52da65 commit 4e91e75
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 24 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
60 changes: 58 additions & 2 deletions src/memory/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ typedef union {

static_assert(sizeof(((chunk_2_t*)0)->fields) <= (size_t)CHUNK_SIZE, "chunk too large");

#if FLASH_APPDATA_LEN / CHUNK_SIZE != 8
#error \
"We expect 8 chunks in app data. This check is to ensure that chunk_7_t below is the last chunk, so it is not erased during reset."
#endif

// CHUNK_7: A chunk that survives device resets (is not erased during `memory_reset_hww()`).
#define CHUNK_7_PERMANENT (7)
typedef union {
struct __attribute__((__packed__)) {
// Hash of bootloader used in the attestation sighash.
// If not set, the actual bootloader area is hashed.
secbool_u8 attestation_bootloader_hash_set;
uint8_t reserved[3];
uint8_t attestation_bootloader_hash[32];
} fields;
uint8_t bytes[CHUNK_SIZE];
} chunk_7_t;

static_assert(sizeof(((chunk_7_t*)0)->fields) <= (size_t)CHUNK_SIZE, "chunk too large");

#pragma GCC diagnostic pop

#define BITMASK_SEEDED ((uint8_t)(1u << 0u))
Expand Down Expand Up @@ -330,8 +350,8 @@ bool memory_cleanup_smarteeprom(void)

bool memory_reset_hww(void)
{
// Erase all app data chunks expect the first one, which is permanent.
for (uint32_t chunk = CHUNK_1; chunk < FLASH_APPDATA_LEN / CHUNK_SIZE; chunk++) {
// Erase all app data chunks expect the first and the last one, which is permanent.
for (uint32_t chunk = CHUNK_1; chunk < (FLASH_APPDATA_LEN / CHUNK_SIZE) - 1; chunk++) {
if (!_write_chunk(chunk, NULL)) {
return false;
}
Expand Down Expand Up @@ -557,6 +577,38 @@ static bool _is_attestation_setup_done(void)
return !MEMEQ(chunk.fields.attestation.certificate, empty, 64);
}

bool memory_set_attestation_bootloader_hash(void)
{
chunk_7_t chunk = {0};
CLEANUP_CHUNK(chunk);
_read_chunk(CHUNK_7_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_7_PERMANENT, chunk.bytes);
}

return true;
}

void memory_get_attestation_bootloader_hash(uint8_t* hash_out)
{
chunk_7_t chunk = {0};
CLEANUP_CHUNK(chunk);
_read_chunk(CHUNK_7_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 +662,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, 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));
}
4 changes: 2 additions & 2 deletions test/unit-test/test_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ static const memory_interface_functions_t _ifs = {

static void _expect_reset(uint8_t* empty_chunk1, uint8_t* empty_chunk2)
{
// Reset all
for (uint32_t write_calls = 0; write_calls < FLASH_APP_DATA_LEN / CHUNK_SIZE - 1;
// Reset all except first and last chunk.
for (uint32_t write_calls = 0; write_calls < FLASH_APP_DATA_LEN / CHUNK_SIZE - 2;
write_calls++) {
expect_value(__wrap_memory_write_chunk_mock, chunk_num, write_calls + 1);
expect_value(__wrap_memory_write_chunk_mock, chunk, NULL);
Expand Down
49 changes: 47 additions & 2 deletions test/unit-test/test_memory_functional.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <memory/memory.h>
#include <mock_memory.h>
#include <random.h>

static void _test_memory_multisig(void** state)
{
Expand Down Expand Up @@ -141,7 +142,7 @@ static void _test_memory_attestation(void** state)
root_pubkey_identifier, expected_root_pubkey_identifier, sizeof(root_pubkey_identifier));
}

void _memory_setup_rand_mock(uint8_t* buf_out)
void _memory_setup_rand_mock_test_functional(uint8_t* buf_out)
{
static uint8_t ctr = 0;
static uint8_t fixtures[][32] = {
Expand Down Expand Up @@ -177,7 +178,7 @@ static void _test_functional(void** state)
mock_memory_factoryreset();

memory_interface_functions_t ifs = {
.random_32_bytes = _memory_setup_rand_mock,
.random_32_bytes = _memory_setup_rand_mock_test_functional,
};
assert_true(memory_setup(&ifs));

Expand Down Expand Up @@ -214,6 +215,49 @@ 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)
{
mock_memory_factoryreset();

memory_interface_functions_t ifs = {
.random_32_bytes = random_32_bytes_mcu,
};
assert_true(memory_setup(&ifs));

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));

assert_true(memory_reset_hww());

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 +266,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 4e91e75

Please sign in to comment.