add collateral erasure and checking of IFR copy of pubkeys

The IFR copy of pubkeys is truly actually indelible because
of the no-exceptions write inhibit byte. Add code to check
against that copy.

Note that the IFR region's downside is the presence of
the write-inhibit byte means the check is complicated
as the full key is striped across two locations, but
this code handles that.
This commit is contained in:
bunnie 2026-01-18 02:56:14 +08:00
parent 24c76b077e
commit 6b7ff11b4e
No known key found for this signature in database
2 changed files with 53 additions and 2 deletions

View file

@ -458,7 +458,7 @@ pub fn compare_refkeys(
csprng: &mut Csprng,
pubkey_ptr: *const bao1x_api::signatures::SignatureInFlash,
fail_counter: usize,
) {
) -> HardenedBool {
let reference_keys =
[bao1x_api::BAO1_PUBKEY, bao1x_api::BAO2_PUBKEY, bao1x_api::BETA_PUBKEY, bao1x_api::DEV_PUBKEY];
// check that the pub keys match those burned into the indelible key area
@ -472,7 +472,22 @@ pub fn compare_refkeys(
good_compare = HardenedBool::FALSE;
}
}
csprng.random_delay();
// The IFR (indelible) copy is weird, because the highest byte doesn't match (it's actually a flag that
// indicates the region has to be write protected). Thus, the IFR keys are a set of four, disjointed
// 31-byte memory areas, plus a collection of 4 bytes that correspond to the missing MSB.
let ifr_keys = [
unsafe { core::slice::from_raw_parts(0x6040_01A0 as *const u8, 31) },
unsafe { core::slice::from_raw_parts(0x6040_01C0 as *const u8, 31) },
unsafe { core::slice::from_raw_parts(0x6040_01E0 as *const u8, 31) },
unsafe { core::slice::from_raw_parts(0x6040_0200 as *const u8, 31) },
];
let ifr_msb = unsafe { core::slice::from_raw_parts(0x6040_0240 as *const u8, 4) };
for (i, (boot0_key, ref_key)) in pk_src.sealed_data.pubkeys.iter().zip(ifr_keys).enumerate() {
if ref_key != &boot0_key.pk[..31] || ifr_msb[i] != boot0_key.pk[31] {
good_compare = HardenedBool::FALSE;
}
}
csprng.random_delay();
match good_compare.is_true() {
Some(false) => {
@ -493,4 +508,5 @@ pub fn compare_refkeys(
None => die(),
}
bollard!(die, 4);
good_compare
}

View file

@ -276,6 +276,41 @@ pub fn erase_secrets(_csprng: &mut Option<&mut Csprng>) -> Result<(), String> {
);
}
#[cfg(not(feature = "std"))]
pub fn erase_collateral(csprng: &mut Option<&mut Csprng>) -> Result<(), String> {
let slot_mgr = SlotManager::new();
let mut rram = crate::rram::Reram::new();
let slot = &bao1x_api::offsets::COLLATERAL;
for data_index in slot.try_into_data_iter().unwrap() {
bollard!(die_no_std, 4);
csprng.as_deref_mut().map(|rng| rng.random_delay());
// only clear ACL if it isn't already cleared
if slot_mgr.get_acl(slot).unwrap().raw_u32() != 0 {
// clear the ACL so we can operate on the data
slot_mgr
.set_acl(&mut rram, slot, &AccessSettings::Data(DataSlotAccess::new_with_raw_value(0)))
.expect("couldn't reset ACL");
}
let bytes = unsafe { slot_mgr.read_data_slot(data_index) };
// only erase if the key hasn't already been erased, to avoid stressing the RRAM array
// erase_secrets() may be called on every boot in some modes.
bollard!(die_no_std, 4);
if !bytes.iter().all(|&b| b == ERASE_VALUE) {
let mut eraser = alloc::vec::Vec::with_capacity(slot.len() * SLOT_ELEMENT_LEN_BYTES);
eraser.resize(slot.len() * SLOT_ELEMENT_LEN_BYTES, ERASE_VALUE);
slot_mgr.write(&mut rram, slot, &eraser).ok();
}
let check = unsafe { slot_mgr.read_data_slot(data_index) };
if !check.iter().all(|&b| b == ERASE_VALUE) {
crate::println!("Failed to erase key at {}: {:x?}", data_index, check);
}
bollard!(die_no_std, 4);
}
Ok(())
}
#[cfg(not(feature = "std"))]
pub fn erase_secrets(csprng: &mut Option<&mut Csprng>) -> Result<(), String> {
// ensure coreuser settings, as we could enter from a variety of loader stages