rollback pddb to previous known good state

restart the refactor process now that the modals arc is finished.
This commit is contained in:
bunnie 2025-05-04 16:40:13 +08:00
parent 142d55a2b7
commit f9c42f6176
6 changed files with 58 additions and 268 deletions

View file

@ -13,7 +13,7 @@ xous-ipc = "0.10.4"
log-server = { package = "xous-api-log", version = "0.1.63" }
ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.63" }
xous-names = { package = "xous-api-names", version = "0.9.65" }
susres = { package = "xous-api-susres", version = "0.9.63", optional = true }
susres = { package = "xous-api-susres", version = "0.9.63" }
log = "0.4.14"
num-derive = { version = "0.4.2", default-features = false }
num-traits = { version = "0.2.14", default-features = false }
@ -21,10 +21,10 @@ rkyv = { version = "0.8.8", default-features = false, features = [
"std",
"alloc",
] }
trng = { path = "../trng", optional = true }
spinor = { path = "../spinor", optional = true }
trng = { path = "../trng" }
spinor = { path = "../spinor" }
aes = { path = "../aes" }
root-keys = { path = "../root-keys", optional = true }
root-keys = { path = "../root-keys" }
cipher = "0.4.2"
bitfield = "0.13.2"
aes-gcm-siv = { version = "0.11.1", default-features = false, features = [
@ -32,13 +32,10 @@ aes-gcm-siv = { version = "0.11.1", default-features = false, features = [
"aes",
] }
# aes-gcm-siv = {git="https://github.com/RustCrypto/AEADs.git", branch="master"}
llio = { path = "../llio", optional = true }
llio = { path = "../llio" }
subtle = { version = "2.4.1", default-features = false }
tts-frontend = { path = "../tts", optional = true }
tts-frontend = { path = "../tts" }
rand_core = "0.6.4"
cram-hal-service = { path = "../cram-hal-service", optional = true }
cramium-emu = { path = "../cramium-emu", optional = true }
cramium-api = { path = "../../libs/cramium-api", optional = true }
# passwords
sha2 = { version = "0.10.8" }
@ -51,10 +48,9 @@ zeroize_derive = "1.4.2"
blowfish = { version = "0.9.1", features = ["bcrypt"] }
# UX (for password entry and notifications)
gam = { path = "../gam", optional = true }
locales = { path = "../../locales", optional = true }
modals = { path = "../modals", optional = true }
ux-api = { path = "../../libs/ux-api", optional = true, features = ["std"] }
gam = { path = "../gam" }
locales = { path = "../../locales" }
modals = { path = "../modals" }
utralib = { version = "0.1.25", optional = true, default-features = false }
@ -69,46 +65,10 @@ hex = { version = "0.4.3", default-features = false, features = [
"alloc",
], optional = true }
# gen 2 dependencies
keystore = { path = "../keystore", optional = true }
xous-swapper = { path = "../xous-swapper", features = [
"spinor",
], optional = true }
[features]
precursor = ["utralib/precursor", "sha2/precursor", "gen1"]
hosted = ["utralib/hosted", "sha2/hosted", "gen1"]
renode = ["utralib/renode", "sha2/renode", "gen1"]
cramium-soc = ["utralib/cramium-soc", "gen2", "cram-hal-service"]
hosted-baosec = ["gen2", "cramium-emu", "cramium-api"]
# gen1 PDDB pulls in the graphical libraries for direct capture of passwords
# from users. This gives maximum control over the flow of sensitive password data,
# but also complicates the code base and dependencies, making it less portable.
# gen2 PDDB focuses on cross-platform portability, keeping the PDDB dependencies tightly
# focused on just doing what the PDDB needs to do from a filesystem perspective. This
# means that the PDDB must inevitably rely upon some less well specified mechanism for
# gathering password materials from the user.
# gen-1 implementation feature set
gen1 = [
"susres",
"trng",
"spinor",
"root-keys",
"llio",
"tts-frontend",
"gam",
"locales",
"modals",
]
# gen-2 features
gen2 = ["keystore"]
# gen-2 boards
board-baosec = ["ux-api/board-baosec", "cramium-api", "gen2", "xous-swapper"]
precursor = ["utralib/precursor", "sha2/precursor"]
hosted = ["utralib/hosted", "sha2/hosted"]
renode = ["utralib/renode", "sha2/renode"]
# when selected, physical disk addresses are set to 64 bits, otherwise, they are 32 bits.
# 32 bit addressing is recommended for Precursor, as its disk is only 128MiB and it has limited RAM for bookkeeping.
u64_pa = []

View file

@ -148,14 +148,12 @@ pub(crate) enum Opcode {
KeyDrop = 20,
/// Menu opcodes
#[cfg(feature = "gen1")]
MenuListBasis = 21,
/// Security state checks
IsEfuseSecured = 22,
/// Suspend/resume callback
#[cfg(feature = "gen1")]
SuspendResume = 23,
/// quit the server
Quit = 24,
@ -217,14 +215,12 @@ pub(crate) enum Opcode {
ListKeyV2 = 45,
/// change unlock PIN
#[cfg(feature = "gen1")]
MenuChangePin = 46,
/// run a test or diagnostic command (depends on the build)
InternalTest = 47,
/// Clear password cache and ask for it again
#[cfg(feature = "gen1")]
UncacheAndAskPassword = 48,
/// Unmount the PDDB.

View file

@ -15,88 +15,15 @@ use aes::{Aes256, BLOCK_SIZE, Block};
use aes_gcm_siv::aead::{Aead, Payload};
use aes_gcm_siv::{Aes256GcmSiv, Nonce, Tag};
use backend::bcrypt::*;
#[cfg(not(any(feature = "hosted-baosec", feature = "board-baosec")))]
use gam::modal::*;
use modals::Modals;
#[cfg(feature = "gen1")]
use root_keys::api::AesRootkeyType;
#[cfg(feature = "gen1")]
use root_keys::api::KeywrapError;
use sha2::{Digest, Sha512_256Hw, Sha512_256Sw};
use spinor::SPINOR_BULK_ERASE_SIZE;
use subtle::ConstantTimeEq;
#[cfg(any(feature = "hosted-baosec", feature = "board-baosec"))]
use ux_api::widgets::*;
use crate::*;
/*
Refactor notes --
What to do about rootkeys? The features used by this crate are:
- selection of an AES key at an index
- password entry to unlock key at index (this is handled inside rootkeys)
- key wrapping using the indexed key
- block decrypt/encrypt with the indexed key
- also queries are made with regards to the key box health and initialization status
What to do about modals?
- The work flow for many of the crates assumes we can have a blocking user I/O
via the modals crate
- I think that the modals crate should probably be the "right" layer for things
to talk to the UI.
Question:
- Can we adapt modals to a small (128x128) pixel display?
- Can we adapt modals to a console-like text interface?
Suggestion:
- Stop the refactor on PDDB at this moment and dig into modals/rootkeys
- Rootkeys can place the keys in RRAM at the "final" location according to ACRAM lock abilities
even if the lifecycle stuff is broken, the ability to read/write those keys are configured
"out of band"
- Modals should include an implementation directly inside the modals crate that pulls in
the mini-gfx library, so that there are no other dependencies to get user I/O going.
Observation: I could check in this partial, broken code right now into a branch, and come back
to it later. As long as I don't select the PDDB crate as a cramium target, it doesn't break
anything else in the code base?
Probably better just to branch this for now - let's make a branch, stick it in xous-core,
and put some notes around it in a WIP pull request. In fact let's put all this data in that
PR so we have some publicly trackable information about how this is progressing.
Xous-swapper refactor notes:
SPINOR shares the same interface as the swap SRAM, and therefore, the Spinor interface
has to live inside xous-swapper.
Xous also needs to be extended to implement Virtual Addresses that are not backed by
physical addresses, but are on-demand allocated by copying SPINOR contents into pages that
are allocated in SRAM. The general idea is something like this:
- A new flag is added that can be passed to xous::MapMemory(). This flag is the `Swapped` flag,
and it indicates to the kernel that the physical address should be `null` and marked as `swapped`
for the mapping in its initial page table map in the given process.
- A "special" range of virtual addresses needs to be carved out which is where the memory-mapped
FLASH memory would go to. Thus, a process would gain access to FLASH by simply calling MapMemory
with the FLASH memory VA range as the virtual address, `None` as the PA, and the Swapped flag
as one of the args in Memory Arguments
- When the virtual address is referenced, it will page fault; the page fault handler will pass
this to Xous-Swapper in the userspace, which will then add a check for the virtual address.
If the virtual address is in the magic range for SPINOR, the contents are fetched using the
SPINOR interface based on the linear mapping of the lower bits of the address onto the SPINOR
memory space.
- WHnever a write happens inside Xous-Swapper to a location in FLASH, the page table entry for
the mapped FLASH location needs to be marked as invalid and returned to the free pool. This
uses the swap system to keep the read-view of FLASH in sync with hte write-view. This also
means that Xous-Swapper's SPINOR map has to keep a scoreboard of what pages are mapped to
what processes, so that we can search for the mapping and clear it whenever a write comes in.
- Development of this can probably happen separately from the emu-layer version. The basic thing
would be to drill down into the kernel with the kernel and a simple test server that just
tries to map the SPINOR and read some contents out, and perhaps update a sector. This will
exercise the path in isolation and allow us to test this routine as a separate primitive
from the PDDB.
*/
#[cfg(not(feature = "deterministic"))]
type FspaceSet = HashSet<PhysPage>;
#[cfg(feature = "deterministic")]
@ -210,14 +137,8 @@ type EmuMemoryRange = xous::MemoryRange;
#[cfg(any(feature = "precursor", feature = "renode"))]
type EmuSpinor = spinor::Spinor;
#[cfg(all(feature = "board-baosec", feature = "gen2"))]
type EmuMemoryRange = xous::MemoryRange;
#[cfg(all(feature = "board-baosec", feature = "gen2"))]
type EmuSpinor = xous_swapper::Spinor;
pub(crate) struct PddbOs {
spinor: EmuSpinor,
#[cfg(feature = "gen1")]
rootkeys: root_keys::RootKeys,
tt: ticktimer_server::Ticktimer,
pddb_mr: EmuMemoryRange,
@ -354,40 +275,6 @@ impl PddbOs {
#[cfg(feature = "perfcounter")]
use_perf: true,
};
#[cfg(any(feature = "cramium-soc"))]
let ret = PddbOs {
spinor: crate::hw::Spinor::new(&xns).unwrap(),
tt: ticktimer_server::Ticktimer::new().unwrap(),
pddb_mr: pddb,
pt_phys_base: PageAlignedPa::from(0 as u32),
key_phys_base,
mbbb_phys_base,
fscb_phys_base,
data_phys_base: PageAlignedPa::from(
fscb_phys_base.as_u32() + FSCB_PAGES as u32 * PAGE_SIZE as u32,
),
system_basis_key: None,
cipher_ecb: None,
fspace_cache: FspaceSet::new(),
fspace_log_addrs: Vec::<PageAlignedPa>::new(),
fspace_log_next_addr: None,
fspace_log_len: 0,
dna,
// default to our own DNA in this case
migration_dna: dna,
dna_mode: DnaMode::Normal,
entropy: trngpool,
pw_cid,
failed_logins: 0,
#[cfg(all(feature = "pddbtest", feature = "autobasis"))]
testnames: HashSet::new(),
#[cfg(feature = "perfcounter")]
perfclient,
#[cfg(feature = "perfcounter")]
pc_id,
#[cfg(feature = "perfcounter")]
use_perf: true,
};
// emulated
#[cfg(not(target_os = "xous"))]
let ret = {
@ -475,14 +362,10 @@ impl PddbOs {
self.pddb_mr.reset();
}
#[cfg(feature = "gen1")]
pub(crate) fn is_efuse_secured(&self) -> bool {
self.rootkeys.is_efuse_secured().expect("couldn't query efuse security state") == Some(true)
}
#[cfg(feature = "gen2")]
pub(crate) fn is_efuse_secured(&self) -> bool { unimplemented!() }
pub(crate) fn nonce_gen(&self) -> Nonce {
let nonce_array = self.entropy.borrow_mut().get_nonce();
*Nonce::from_slice(&nonce_array)
@ -500,16 +383,10 @@ impl PddbOs {
pub(crate) fn timestamp_now(&self) -> u64 { self.tt.elapsed_ms() }
/// checks if the root keys are initialized, which is a prerequisite to formatting and mounting
#[cfg(feature = "gen1")]
pub(crate) fn rootkeys_initialized(&self) -> bool {
self.rootkeys.is_initialized().expect("couldn't query initialization state of the rootkeys server")
}
#[cfg(feature = "gen2")]
pub(crate) fn rootkeys_initialized(&self) -> bool {
unimplemented!();
}
/// patches data at an offset starting from the data physical base address, which corresponds
/// exactly to the first entry in the page table
pub(crate) fn patch_data(&self, data: &[u8], offset: u32) {
@ -894,12 +771,8 @@ impl PddbOs {
self.cipher_ecb = None;
}
#[cfg(feature = "gen1")]
pub(crate) fn clear_password(&self) { self.rootkeys.clear_password(AesRootkeyType::User0); }
#[cfg(feature = "gen2")]
pub(crate) fn clear_password(&self) { todo!("implement password clearing") }
pub(crate) fn try_login(&mut self) -> PasswordState {
use aes::cipher::KeyInit;
if self.system_basis_key.is_none() || self.cipher_ecb.is_none() {
@ -938,49 +811,42 @@ impl PddbOs {
// now try to populate our keys, and prep a migration if necessary
let mut syskey_pt = [0u8; 32];
let mut syskey = [0u8; 32];
#[cfg(feature = "gen1")]
{
let mut keys_updated = false;
match self.rootkeys.unwrap_key(&scd.system_key_pt, AES_KEYSIZE) {
Ok(skpt) => syskey_pt.copy_from_slice(&skpt),
Err(e) => match e {
KeywrapError::UpgradeToNew((key, upgrade)) => {
log::warn!("pt key migration required");
syskey_pt.copy_from_slice(&key);
new_crypto_keys.system_key_pt.copy_from_slice(&upgrade);
keys_updated = true;
}
_ => {
log::error!("Couldn't unwrap our system key: {:?}", e);
self.failed_logins = self.failed_logins.saturating_add(1);
return PasswordState::Incorrect(self.failed_logins);
}
},
}
match self.rootkeys.unwrap_key(&scd.system_key, AES_KEYSIZE) {
Ok(sk) => syskey.copy_from_slice(&sk),
Err(e) => match e {
KeywrapError::UpgradeToNew((key, upgrade)) => {
log::warn!("data key migration required");
syskey.copy_from_slice(&key);
new_crypto_keys.system_key.copy_from_slice(&upgrade);
keys_updated = true;
}
_ => {
log::error!("Couldn't unwrap our system key: {:?}", e);
self.failed_logins = self.failed_logins.saturating_add(1);
return PasswordState::Incorrect(self.failed_logins);
}
},
}
if keys_updated {
log::warn!("Migration event from incorrectly wrapped key");
self.patch_keys(new_crypto_keys.deref(), 0);
}
let mut keys_updated = false;
match self.rootkeys.unwrap_key(&scd.system_key_pt, AES_KEYSIZE) {
Ok(skpt) => syskey_pt.copy_from_slice(&skpt),
Err(e) => match e {
KeywrapError::UpgradeToNew((key, upgrade)) => {
log::warn!("pt key migration required");
syskey_pt.copy_from_slice(&key);
new_crypto_keys.system_key_pt.copy_from_slice(&upgrade);
keys_updated = true;
}
_ => {
log::error!("Couldn't unwrap our system key: {:?}", e);
self.failed_logins = self.failed_logins.saturating_add(1);
return PasswordState::Incorrect(self.failed_logins);
}
},
}
#[cfg(feature = "gen2")]
{
todo!("implement password entry")
match self.rootkeys.unwrap_key(&scd.system_key, AES_KEYSIZE) {
Ok(sk) => syskey.copy_from_slice(&sk),
Err(e) => match e {
KeywrapError::UpgradeToNew((key, upgrade)) => {
log::warn!("data key migration required");
syskey.copy_from_slice(&key);
new_crypto_keys.system_key.copy_from_slice(&upgrade);
keys_updated = true;
}
_ => {
log::error!("Couldn't unwrap our system key: {:?}", e);
self.failed_logins = self.failed_logins.saturating_add(1);
return PasswordState::Incorrect(self.failed_logins);
}
},
}
if keys_updated {
log::warn!("Migration event from incorrectly wrapped key");
self.patch_keys(new_crypto_keys.deref(), 0);
}
let cipher = Aes256::new(GenericArray::from_slice(&syskey_pt));
@ -1013,18 +879,11 @@ impl PddbOs {
fn syskey_ensure(&mut self) {
while self.try_login() != PasswordState::Correct {
self.clear_password(); // clear the bad password entry
#[cfg(feature = "gen1")]
{
let xns = xous_names::XousNames::new().unwrap();
let modals = modals::Modals::new(&xns).expect("can't connect to Modals server");
modals
.show_notification(t!("pddb.badpass_infallible", locales::LANG), None)
.expect("notification failed");
}
#[cfg(feature = "gen2")]
{
todo!("implement feedback that syskey is not available");
}
let xns = xous_names::XousNames::new().unwrap();
let modals = modals::Modals::new(&xns).expect("can't connect to Modals server");
modals
.show_notification(t!("pddb.badpass_infallible", locales::LANG), None)
.expect("notification failed");
}
}
@ -2117,7 +1976,6 @@ impl PddbOs {
}
}
#[cfg(feature = "gen1")]
fn pw_check(&self, modals: &modals::Modals) -> Result<()> {
let mut success = false;
while !success {
@ -2150,7 +2008,6 @@ impl PddbOs {
}
/// this function attempts to change the PIN. returns Ok() if changed, error if not.
#[cfg(feature = "gen1")]
pub(crate) fn pddb_change_pin(&mut self, modals: &modals::Modals) -> Result<()> {
if let Some(system_keys) = &self.system_basis_key {
// get the new password

View file

@ -21,11 +21,6 @@ use core::ops::DerefMut;
use std::cell::RefCell;
use std::convert::TryInto;
#[cfg(all(feature = "gen2", feature = "cramium-soc"))]
use cram_hal_service::trng;
#[cfg(all(feature = "gen2", feature = "hosted-baosec"))]
use cramium_emu::trng;
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)]
pub enum CbOp {
Change,
@ -268,7 +263,6 @@ impl Pddb {
/// Computes checksums on the entire PDDB database. This operation can take some time and causes a
/// progress bar to pop up. This should be called only after the PDDB has been unmounted, to ensure
/// that the disk contents do not change after the checksums have been computed.
#[cfg(feature = "gen1")]
pub fn compute_checksums(&self) -> root_keys::api::Checksums {
let alloc = root_keys::api::Checksums::default();
let mut buf = Buffer::into_buf(alloc).expect("Couldn't convert memory structure");

View file

@ -1,3 +1,6 @@
#![cfg_attr(target_os = "none", no_std)]
#![cfg_attr(target_os = "none", no_main)]
extern crate bitfield;
/// # PDDB - Plausibly Deniable DataBase
///
@ -369,9 +372,7 @@ use backend::*;
mod ux;
use rkyv::with::Identity;
use ux::*;
#[cfg(feature = "gen1")]
mod menu;
#[cfg(feature = "gen1")]
use menu::*;
mod libstd;
@ -398,10 +399,7 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
#[cfg(not(any(feature = "hosted-baosec", feature = "cramium-soc")))]
use gam::*;
use locales::t;
use modals::Modals;
use num_traits::*;
use rkyv::{
Archive, Place, Serialize,
@ -410,8 +408,6 @@ use rkyv::{
ser::{Positional, allocator::SubAllocator, writer::Buffer as RkyvBuffer},
with::{ArchiveWith, SerializeWith},
};
#[cfg(any(feature = "hosted-baosec", feature = "cramium-soc"))]
use ux_api::widgets::*;
use xous::{Message, msg_blocking_scalar_unpack, send_message};
use xous_ipc::Buffer;
@ -508,7 +504,7 @@ fn wrapped_main() -> ! {
let entropy = Rc::new(RefCell::new(TrngPool::new()));
// for less-secured user prompts (everything but password entry)
let modals = Modals::new(&xns).expect("can't connect to Modals server");
let modals = modals::Modals::new(&xns).expect("can't connect to Modals server");
// our very own password modal. Password modals are precious and privately owned, to avoid
// other processes from crafting them.
@ -559,9 +555,8 @@ fn wrapped_main() -> ! {
}
});
let my_cid = xous::connect(pddb_sid).unwrap();
// our menu handler
#[cfg(feature = "gen1")]
let my_cid = xous::connect(pddb_sid).unwrap();
let _ = thread::spawn({
let my_cid = my_cid.clone();
move || {
@ -669,7 +664,6 @@ fn wrapped_main() -> ! {
let mut scratch = [MaybeUninit::<u8>::uninit(); 256];
// register a suspend/resume listener
#[cfg(feature = "gen1")]
let mut susres =
susres::Susres::new(Some(susres::SuspendOrder::Early), &xns, Opcode::SuspendResume as u32, my_cid)
.expect("couldn't create suspend/resume object");
@ -678,7 +672,6 @@ fn wrapped_main() -> ! {
let op: Opcode = FromPrimitive::from_usize(msg.body.id() & 0xffff).unwrap_or(Opcode::InvalidOpcode);
log::debug!("{:x?}", op);
match op {
#[cfg(feature = "gen1")]
Opcode::SuspendResume => xous::msg_scalar_unpack!(msg, token, _, _, _, {
basis_cache.suspend(&mut pddb_os);
susres.suspend_until_resume(token).expect("couldn't execute suspend/resume");
@ -722,9 +715,7 @@ fn wrapped_main() -> ! {
// If we need more nuance out of this routine, consider creating a custom public enum type to help
// marshall this.
Opcode::TryMount => xous::msg_blocking_scalar_unpack!(msg, _, _, _, _, {
#[cfg(feature = "gen1")]
let llio = llio::Llio::new(&xns);
#[cfg(feature = "gen1")]
while !llio.is_ec_ready() {
// spin-wait while the EC is not ready -- so that a PDDB mount does not interfere with
// on-going EC updates. Also causes the mount dialog to delay until the EC status has been
@ -738,9 +729,7 @@ fn wrapped_main() -> ! {
// can't mount if we have no root keys
log::info!("{}PDDB.SKIPMOUNT,{}", xous::BOOKEND_START, xous::BOOKEND_END);
// allow the main menu to be used in this case
#[cfg(feature = "gen1")]
let gam = gam::Gam::new(&xns).unwrap();
#[cfg(feature = "gen1")]
gam.allow_mainmenu().expect("couldn't allow main menu activation");
xous::return_scalar2(msg.sender, 1, 0).expect("could't return scalar");
} else {
@ -810,9 +799,7 @@ fn wrapped_main() -> ! {
}
// get a handle to the GAM and inform it that main menu should be allowed. The handle
// is dropped when this routine finishes.
#[cfg(feature = "gen1")]
let gam = gam::Gam::new(&xns).unwrap();
#[cfg(feature = "gen1")]
gam.allow_mainmenu().expect("couldn't allow main menu activation");
// setup the heap
initial_heap = heap_usage();
@ -2401,7 +2388,6 @@ fn ensure_password(modals: &modals::Modals, pddb_os: &mut PddbOs, _pw_cid: xous:
}
}
}
fn try_mount_or_format(
modals: &modals::Modals,
pddb_os: &mut PddbOs,

View file

@ -1,9 +1,6 @@
#[cfg(not(any(feature = "hosted-baosec", feature = "cramium-soc")))]
use gam::modal::*;
use locales::t;
use num_traits::*;
#[cfg(any(feature = "hosted-baosec", feature = "cramium-soc"))]
use ux_api::widgets::*;
use xous::msg_scalar_unpack;
use xous::{CID, SID};
use xous_ipc::Buffer;
@ -119,7 +116,7 @@ pub(crate) fn password_ux_manager(
}
Some(PwManagerOpcode::PwReturn) => {
let mut buf = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
let pw = buf.to_original::<TextEntryPayloads, _>().unwrap();
let pw = buf.to_original::<gam::modal::TextEntryPayloads, _>().unwrap();
if let Some(mut response) = dr.take() {
let mut buffer = unsafe {