Proof-of-concept proving the secure-storage exit criterion from docs/architecture/proof-of-concept-plan.md §2: "Secret write/read/delete works through platform secure storage." Implements a typed SecretStorageRepository trait per ADR-006 (SecureStore + per-platform adapters) and a Linux adapter (the only adapter in PoC scope) that supports both equivalent Linux backends per SysRS-053/SysRS-162: Secret Service (libsecret) and kernel keyutils. The audit test suite covers: SS-AUD-001 identity secret absent from local DB (raw file scan) SS-AUD-002 server password absent from local DB SS-AUD-003 secrets absent from logs (Secret newtype redaction) SS-AUD-005 failure returns safe typed error (NotFound) SS-AUD-006 delete removes entry SS-TC-003 Linux round-trip set/get/delete Verified on 2026-05-13 against the local keyutils backend (cargo test runs need 'keyctl session -' to provide a valid session keyring under non-interactive shells, documented in the spike README). The CLI driver additionally observed a real locked gnome-keyring collection and exercised the typed-error → fallback path live. Surfaced finding for the decision register: DEC-013 does not pin a Linux secure-storage backend policy. Both Secret Service and keyutils are 'equivalent' per the requirements; production code needs an owner ruling. Out of scope: Windows DPAPI, macOS/iOS Keychain, Android Keystore, SS-AUD-004 (covered by diagnostics-redaction spike), SS-AUD-007/008 (process / migration items). Authority: PoC plan §2, ADR-006, SDD-078, SRS-091..095, SysRS-158..162. Not product code; not promoted into chanora_storage.
81 lines
2.8 KiB
Rust
81 lines
2.8 KiB
Rust
//! CLI driver — exercises the full secret round-trip against the real
|
|
//! platform secure storage.
|
|
|
|
use std::process::ExitCode;
|
|
|
|
use secure_storage_spike::secret::Secret;
|
|
use secure_storage_spike::SecretStorageRepository;
|
|
use tracing::info;
|
|
|
|
const SERVICE: &str = "app.chanora.poc.secure-storage";
|
|
|
|
fn main() -> ExitCode {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
|
)
|
|
.init();
|
|
|
|
if let Err(e) = run() {
|
|
eprintln!("error: {e}");
|
|
return ExitCode::from(1);
|
|
}
|
|
ExitCode::SUCCESS
|
|
}
|
|
|
|
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
#[cfg(not(target_os = "linux"))]
|
|
{
|
|
eprintln!("This PoC currently only ships a Linux adapter.");
|
|
return Err("unsupported platform".into());
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
use secure_storage_spike::secret::linux::LinuxSecureStore;
|
|
|
|
// Try Secret Service first (preferred per ADR-006); fall back to
|
|
// keyutils if the default collection is locked or unavailable.
|
|
let store: Box<dyn SecretStorageRepository> = {
|
|
let ss = LinuxSecureStore::new(SERVICE);
|
|
let probe = ss.set("__chanora_probe__", &Secret::from_utf8("probe"));
|
|
match probe {
|
|
Ok(()) => {
|
|
let _ = ss.delete("__chanora_probe__");
|
|
info!(target: "spike", "using Secret Service backend");
|
|
Box::new(ss)
|
|
}
|
|
Err(e) => {
|
|
info!(target: "spike", "Secret Service unavailable ({e}); falling back to keyutils");
|
|
Box::new(LinuxSecureStore::new_keyutils(SERVICE))
|
|
}
|
|
}
|
|
};
|
|
|
|
let name = "identity.primary";
|
|
|
|
info!(target: "spike", "writing secret {}", name);
|
|
let original = Secret::from_utf8("MG0DAgeAAgEgAiAIXJBlj1hQbaH0Eq0DuLlCmH8bl+veTAO2");
|
|
store.set(name, &original)?;
|
|
|
|
info!(target: "spike", "reading back");
|
|
let round_trip = store.get(name)?;
|
|
assert_eq!(original, round_trip, "round-trip mismatch");
|
|
info!(target: "spike", "round-trip OK ({} bytes)", round_trip.len());
|
|
|
|
info!(target: "spike", "deleting");
|
|
store.delete(name)?;
|
|
match store.get(name) {
|
|
Err(secure_storage_spike::SecureStoreError::NotFound) => {
|
|
info!(target: "spike", "post-delete read correctly returned NotFound");
|
|
}
|
|
Ok(_) => return Err("secret still readable after delete".into()),
|
|
Err(e) => return Err(format!("unexpected error after delete: {e}").into()),
|
|
}
|
|
|
|
println!("OK — Linux Secret Service round-trip verified.");
|
|
Ok(())
|
|
}
|
|
}
|