Skip to content

Commit 4e91e75

Browse files
committed
Merge remote-tracking branch 'benma/hash'
2 parents 128c1f2 + d52da65 commit 4e91e75

File tree

11 files changed

+183
-24
lines changed

11 files changed

+183
-24
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ add_custom_target(rust-bindgen
298298
--allowlist-function memory_is_seeded
299299
--allowlist-function memory_is_mnemonic_passphrase_enabled
300300
--allowlist-function memory_get_attestation_pubkey_and_certificate
301+
--allowlist-function memory_get_attestation_bootloader_hash
301302
--allowlist-function memory_bootloader_hash
302303
--allowlist-function memory_get_noise_static_private_key
303304
--allowlist-function memory_check_noise_remote_static_pubkey

src/factorysetup.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ static void _api_msg(const Packet* in_packet, Packet* out_packet, const size_t m
147147
switch (input[0]) {
148148
case OP_BOOTLOADER_HASH:
149149
memory_bootloader_hash(output + 2);
150+
// This is the hash that will be used for the attestation, persist for later use.
151+
if (!memory_set_attestation_bootloader_hash()) {
152+
screen_print_debug("setting attestation bootloader hash failed", 0);
153+
}
150154
out_len = 2 + 32;
151155
break;
152156
case OP_GENKEY: {

src/memory/memory.c

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,26 @@ typedef union {
120120

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

123+
#if FLASH_APPDATA_LEN / CHUNK_SIZE != 8
124+
#error \
125+
"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."
126+
#endif
127+
128+
// CHUNK_7: A chunk that survives device resets (is not erased during `memory_reset_hww()`).
129+
#define CHUNK_7_PERMANENT (7)
130+
typedef union {
131+
struct __attribute__((__packed__)) {
132+
// Hash of bootloader used in the attestation sighash.
133+
// If not set, the actual bootloader area is hashed.
134+
secbool_u8 attestation_bootloader_hash_set;
135+
uint8_t reserved[3];
136+
uint8_t attestation_bootloader_hash[32];
137+
} fields;
138+
uint8_t bytes[CHUNK_SIZE];
139+
} chunk_7_t;
140+
141+
static_assert(sizeof(((chunk_7_t*)0)->fields) <= (size_t)CHUNK_SIZE, "chunk too large");
142+
123143
#pragma GCC diagnostic pop
124144

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

331351
bool memory_reset_hww(void)
332352
{
333-
// Erase all app data chunks expect the first one, which is permanent.
334-
for (uint32_t chunk = CHUNK_1; chunk < FLASH_APPDATA_LEN / CHUNK_SIZE; chunk++) {
353+
// Erase all app data chunks expect the first and the last one, which is permanent.
354+
for (uint32_t chunk = CHUNK_1; chunk < (FLASH_APPDATA_LEN / CHUNK_SIZE) - 1; chunk++) {
335355
if (!_write_chunk(chunk, NULL)) {
336356
return false;
337357
}
@@ -557,6 +577,38 @@ static bool _is_attestation_setup_done(void)
557577
return !MEMEQ(chunk.fields.attestation.certificate, empty, 64);
558578
}
559579

580+
bool memory_set_attestation_bootloader_hash(void)
581+
{
582+
chunk_7_t chunk = {0};
583+
CLEANUP_CHUNK(chunk);
584+
_read_chunk(CHUNK_7_PERMANENT, chunk_bytes);
585+
uint8_t empty[32];
586+
memset(empty, 0xff, sizeof(empty));
587+
if (chunk.fields.attestation_bootloader_hash_set != sectrue_u8 ||
588+
MEMEQ(chunk.fields.attestation_bootloader_hash, empty, sizeof(empty))) {
589+
chunk.fields.attestation_bootloader_hash_set = sectrue_u8;
590+
memory_bootloader_hash(chunk.fields.attestation_bootloader_hash);
591+
return _write_chunk(CHUNK_7_PERMANENT, chunk.bytes);
592+
}
593+
594+
return true;
595+
}
596+
597+
void memory_get_attestation_bootloader_hash(uint8_t* hash_out)
598+
{
599+
chunk_7_t chunk = {0};
600+
CLEANUP_CHUNK(chunk);
601+
_read_chunk(CHUNK_7_PERMANENT, chunk_bytes);
602+
uint8_t empty[32];
603+
memset(empty, 0xff, sizeof(empty));
604+
if (chunk.fields.attestation_bootloader_hash_set != sectrue_u8 ||
605+
MEMEQ(chunk.fields.attestation_bootloader_hash, empty, sizeof(empty))) {
606+
memory_bootloader_hash(hash_out);
607+
return;
608+
}
609+
memcpy(hash_out, chunk.fields.attestation_bootloader_hash, 32);
610+
}
611+
560612
bool memory_set_attestation_device_pubkey(const uint8_t* attestation_device_pubkey)
561613
{
562614
chunk_0_t chunk = {0};
@@ -610,9 +662,13 @@ bool memory_get_attestation_pubkey_and_certificate(
610662

611663
void memory_bootloader_hash(uint8_t* hash_out)
612664
{
665+
#ifdef TESTING
666+
memory_bootloader_hash_mock(hash_out);
667+
#else
613668
uint8_t* bootloader = FLASH_BOOT_START;
614669
size_t len = FLASH_BOOT_LEN - 32; // 32 bytes are random
615670
rust_sha256(bootloader, len, hash_out);
671+
#endif
616672
}
617673

618674
bool memory_bootloader_set_flags(auto_enter_t auto_enter, upside_down_t upside_down)

src/memory/memory.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,17 @@ void memory_get_io_protection_key(uint8_t* key_out);
146146
void memory_get_authorization_key(uint8_t* key_out);
147147
void memory_get_encryption_key(uint8_t* key_out);
148148

149+
/**
150+
* Persists the current bootloader hash, which is part of the attestation sighash.
151+
*/
152+
USE_RESULT bool memory_set_attestation_bootloader_hash(void);
153+
154+
/**
155+
* Retreives the bootloader hash that is part of the attestation sighash.
156+
* @param[out] hash_out must be 32 bytes and will contain the result.
157+
*/
158+
void memory_get_attestation_bootloader_hash(uint8_t* hash_out);
159+
149160
/**
150161
* Persists the given attestation device pubkey.
151162
* @param[in] attestation_device_pubkey P256 NIST ECC pubkey (X and Y coordinates).

src/rust/bitbox02-rust/src/attestation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn perform(host_challenge: [u8; 32]) -> Result<Data, ()> {
3636
&mut result.root_pubkey_identifier,
3737
)?;
3838
let hash: [u8; 32] = Sha256::digest(host_challenge).into();
39-
bitbox02::memory::bootloader_hash(&mut result.bootloader_hash);
39+
result.bootloader_hash = bitbox02::memory::get_attestation_bootloader_hash();
4040
bitbox02::securechip::attestation_sign(&hash, &mut result.challenge_signature)?;
4141
Ok(result)
4242
}

src/rust/bitbox02/src/memory.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ pub fn is_mnemonic_passphrase_enabled() -> bool {
6565
unsafe { bitbox02_sys::memory_is_mnemonic_passphrase_enabled() }
6666
}
6767

68+
pub fn get_attestation_bootloader_hash() -> [u8; 32] {
69+
let mut hash = [0u8; 32];
70+
unsafe {
71+
bitbox02_sys::memory_get_attestation_bootloader_hash(hash.as_mut_ptr());
72+
}
73+
hash
74+
}
75+
6876
pub fn get_attestation_pubkey_and_certificate(
6977
device_pubkey: &mut [u8; 64],
7078
certificate: &mut [u8; 64],
@@ -82,12 +90,6 @@ pub fn get_attestation_pubkey_and_certificate(
8290
}
8391
}
8492

85-
pub fn bootloader_hash(out: &mut [u8; 32]) {
86-
unsafe {
87-
bitbox02_sys::memory_bootloader_hash(out.as_mut_ptr());
88-
}
89-
}
90-
9193
pub fn get_noise_static_private_key() -> Result<zeroize::Zeroizing<[u8; 32]>, ()> {
9294
let mut out = zeroize::Zeroizing::new([0u8; 32]);
9395
match unsafe { bitbox02_sys::memory_get_noise_static_private_key(out.as_mut_ptr()) } {
@@ -157,3 +159,14 @@ pub fn multisig_get_by_hash(hash: &[u8]) -> Option<String> {
157159
false => None,
158160
}
159161
}
162+
163+
#[cfg(test)]
164+
mod tests {
165+
use super::*;
166+
167+
#[test]
168+
fn test_get_attestation_bootloader_hash() {
169+
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";
170+
assert_eq!(get_attestation_bootloader_hash(), expected);
171+
}
172+
}

test/simulator/framework/mock_memory.c

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,35 @@ static uint8_t* _get_memory(uint32_t base)
5151
bool memory_write_to_address_mock(uint32_t base, uint32_t addr, const uint8_t* chunk)
5252
{
5353
if (chunk == NULL) {
54-
memset(_get_memory(base) + addr, 0xff, CHUNK_SIZE);
54+
memset(_get_memory(base) + addr, 0xff, (size_t)CHUNK_SIZE);
5555
} else {
56-
memcpy(_get_memory(base) + addr, chunk, CHUNK_SIZE);
56+
memcpy(_get_memory(base) + addr, chunk, (size_t)CHUNK_SIZE);
5757
}
5858
return true;
5959
}
6060

6161
bool memory_write_chunk_mock(uint32_t chunk_num, const uint8_t* chunk)
6262
{
63-
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * CHUNK_SIZE, chunk);
63+
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * (size_t)CHUNK_SIZE, chunk);
6464
}
6565

6666
void memory_read_chunk_mock(uint32_t chunk_num, uint8_t* chunk_out)
6767
{
68-
memcpy(chunk_out, _memory_app_data + chunk_num * CHUNK_SIZE, CHUNK_SIZE);
68+
memcpy(chunk_out, _memory_app_data + chunk_num * (size_t)CHUNK_SIZE, (size_t)CHUNK_SIZE);
6969
}
7070

7171
void memory_read_shared_bootdata_mock(uint8_t* chunk_out)
7272
{
73-
memcpy(chunk_out, _memory_shared_data, FLASH_SHARED_DATA_LEN);
74-
}
73+
memcpy(chunk_out, _memory_shared_data, (size_t)FLASH_SHARED_DATA_LEN);
74+
}
75+
76+
// Arbitrary value.
77+
static uint8_t _bootloader_hash[32] =
78+
"\x71\x3d\xf0\xd5\x8c\x71\x7d\x40\x31\x78\x7c\xdc\x8f\xa3\x5b\x90\x25\x82\xbe\x6a\xb6\xa2\x2e"
79+
"\x09\xde\x44\x77\xd3\x0e\x22\x30\xfc";
80+
81+
void memory_bootloader_hash_mock(uint8_t* hash_out)
82+
{
83+
// NOLINTNEXTLINE(bugprone-not-null-terminated-result)
84+
memcpy(hash_out, _bootloader_hash, 32);
85+
}

test/unit-test/framework/includes/mock_memory.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ void memory_read_chunk_mock(uint32_t chunk_num, uint8_t* chunk_out);
2727
// Size: `FLASH_SHARED_DATA_LEN`.
2828
void memory_read_shared_bootdata_mock(uint8_t* chunk_out);
2929
void mock_memory_set_salt_root(const uint8_t* salt_root);
30+
void memory_bootloader_hash_mock(uint8_t* hash_out);
31+
void memory_set_bootloader_hash_mock(const uint8_t* mock_hash);
3032
#endif

test/unit-test/framework/mock_memory.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,26 @@ static uint8_t* _get_memory(uint32_t base)
5252
bool memory_write_to_address_mock(uint32_t base, uint32_t addr, const uint8_t* chunk)
5353
{
5454
if (chunk == NULL) {
55-
memset(_get_memory(base) + addr, 0xff, CHUNK_SIZE);
55+
memset(_get_memory(base) + addr, 0xff, (size_t)CHUNK_SIZE);
5656
} else {
57-
memcpy(_get_memory(base) + addr, chunk, CHUNK_SIZE);
57+
memcpy(_get_memory(base) + addr, chunk, (size_t)CHUNK_SIZE);
5858
}
5959
return true;
6060
}
6161

6262
bool memory_write_chunk_mock(uint32_t chunk_num, const uint8_t* chunk)
6363
{
64-
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * CHUNK_SIZE, chunk);
64+
return memory_write_to_address_mock(FLASH_APPDATA_START, chunk_num * (size_t)CHUNK_SIZE, chunk);
6565
}
6666

6767
void memory_read_chunk_mock(uint32_t chunk_num, uint8_t* chunk_out)
6868
{
69-
memcpy(chunk_out, _memory_app_data + chunk_num * CHUNK_SIZE, CHUNK_SIZE);
69+
memcpy(chunk_out, _memory_app_data + chunk_num * (size_t)CHUNK_SIZE, (size_t)CHUNK_SIZE);
7070
}
7171

7272
void memory_read_shared_bootdata_mock(uint8_t* chunk_out)
7373
{
74-
memcpy(chunk_out, _memory_shared_data, FLASH_SHARED_DATA_LEN);
74+
memcpy(chunk_out, _memory_shared_data, (size_t)FLASH_SHARED_DATA_LEN);
7575
}
7676

7777
bool __wrap_memory_is_initialized(void)
@@ -132,3 +132,19 @@ bool __wrap_memory_get_salt_root(uint8_t* salt_root_out)
132132
memcpy(salt_root_out, _salt_root, 32);
133133
return true;
134134
}
135+
136+
// Arbitrary value.
137+
static uint8_t _bootloader_hash[32] =
138+
"\x71\x3d\xf0\xd5\x8c\x71\x7d\x40\x31\x78\x7c\xdc\x8f\xa3\x5b\x90\x25\x82\xbe\x6a\xb6\xa2\x2e"
139+
"\x09\xde\x44\x77\xd3\x0e\x22\x30\xfc";
140+
141+
void memory_bootloader_hash_mock(uint8_t* hash_out)
142+
{
143+
memcpy(hash_out, _bootloader_hash, 32);
144+
}
145+
146+
void memory_set_bootloader_hash_mock(const uint8_t* mock_hash)
147+
{
148+
// NOLINTNEXTLINE(bugprone-not-null-terminated-result)
149+
memcpy(_bootloader_hash, mock_hash, sizeof(_bootloader_hash));
150+
}

test/unit-test/test_memory.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ static const memory_interface_functions_t _ifs = {
114114

115115
static void _expect_reset(uint8_t* empty_chunk1, uint8_t* empty_chunk2)
116116
{
117-
// Reset all
118-
for (uint32_t write_calls = 0; write_calls < FLASH_APP_DATA_LEN / CHUNK_SIZE - 1;
117+
// Reset all except first and last chunk.
118+
for (uint32_t write_calls = 0; write_calls < FLASH_APP_DATA_LEN / CHUNK_SIZE - 2;
119119
write_calls++) {
120120
expect_value(__wrap_memory_write_chunk_mock, chunk_num, write_calls + 1);
121121
expect_value(__wrap_memory_write_chunk_mock, chunk, NULL);

0 commit comments

Comments
 (0)