From 42b746f9afff0f4f883e3bb23a0967b389547b1e Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 3 May 2026 20:56:39 -0400 Subject: [PATCH] feat(wasm): session stores image_secret for recovery QR generation --- crates/relicario-wasm/src/lib.rs | 10 ++++++---- crates/relicario-wasm/src/session.rs | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/crates/relicario-wasm/src/lib.rs b/crates/relicario-wasm/src/lib.rs index ff1bed6..7ef8569 100644 --- a/crates/relicario-wasm/src/lib.rs +++ b/crates/relicario-wasm/src/lib.rs @@ -8,6 +8,7 @@ mod session; mod device; use wasm_bindgen::prelude::*; +use zeroize::Zeroizing; use relicario_core::{derive_master_key, imgsecret, KdfParams}; @@ -36,7 +37,8 @@ pub fn unlock( .map_err(|_| JsError::new("salt must be exactly 32 bytes"))?; let master_key = derive_master_key(passphrase.as_bytes(), &image_secret, salt_arr, ¶ms) .map_err(|e| JsError::new(&e.to_string()))?; - let handle = session::insert(master_key); + let stored_secret = Zeroizing::new(image_secret); + let handle = session::insert(master_key, stored_secret); Ok(SessionHandle(handle)) } @@ -492,7 +494,7 @@ mod session_tests { #[test] fn insert_then_remove_clears_entry() { session::clear(); - let h = session::insert(Zeroizing::new([0x11u8; 32])); + let h = session::insert(Zeroizing::new([0x11u8; 32]), Zeroizing::new([0u8; 32])); assert_ne!(h, 0); assert!(session::remove(h)); assert!(!session::remove(h)); // second remove false @@ -501,7 +503,7 @@ mod session_tests { #[test] fn with_yields_key_only_while_session_lives() { session::clear(); - let h = session::insert(Zeroizing::new([0x22u8; 32])); + let h = session::insert(Zeroizing::new([0x22u8; 32]), Zeroizing::new([0u8; 32])); let byte = session::with(h, |k| k[0]); assert_eq!(byte, Some(0x22)); session::remove(h); @@ -513,7 +515,7 @@ mod session_tests { fn manifest_round_trip_via_handle() { use relicario_core::{Manifest, decrypt_manifest}; session::clear(); - let h = session::insert(Zeroizing::new([0x55u8; 32])); + let h = session::insert(Zeroizing::new([0x55u8; 32]), Zeroizing::new([0u8; 32])); let handle = SessionHandle(h); let key = Zeroizing::new([0x55u8; 32]); let empty = Manifest::new(); diff --git a/crates/relicario-wasm/src/session.rs b/crates/relicario-wasm/src/session.rs index 6b553ad..ae084ba 100644 --- a/crates/relicario-wasm/src/session.rs +++ b/crates/relicario-wasm/src/session.rs @@ -6,12 +6,17 @@ use std::cell::RefCell; use std::collections::HashMap; use zeroize::Zeroizing; +pub struct SessionData { + pub master_key: Zeroizing<[u8; 32]>, + pub image_secret: Zeroizing<[u8; 32]>, +} + thread_local! { - static SESSIONS: RefCell>> = RefCell::new(HashMap::new()); + static SESSIONS: RefCell> = RefCell::new(HashMap::new()); static NEXT_HANDLE: RefCell = const { RefCell::new(1) }; } -pub fn insert(key: Zeroizing<[u8; 32]>) -> u32 { +pub fn insert(master_key: Zeroizing<[u8; 32]>, image_secret: Zeroizing<[u8; 32]>) -> u32 { let handle = NEXT_HANDLE.with(|n| { let mut n = n.borrow_mut(); let h = *n; @@ -19,15 +24,26 @@ pub fn insert(key: Zeroizing<[u8; 32]>) -> u32 { if *n == 0 { *n = 1; } // avoid reserving 0 as a valid handle h }); - SESSIONS.with(|s| { s.borrow_mut().insert(handle, key); }); + SESSIONS.with(|s| { + s.borrow_mut().insert(handle, SessionData { master_key, image_secret }); + }); handle } +/// Access the master key for a handle. Preserves original `with` signature for all existing callers. pub fn with(handle: u32, f: F) -> Option where F: FnOnce(&Zeroizing<[u8; 32]>) -> R, { - SESSIONS.with(|s| s.borrow().get(&handle).map(f)) + SESSIONS.with(|s| s.borrow().get(&handle).map(|d| f(&d.master_key))) +} + +/// Access the image_secret for a handle (used by recovery QR). +pub fn with_image_secret(handle: u32, f: F) -> Option +where + F: FnOnce(&Zeroizing<[u8; 32]>) -> R, +{ + SESSIONS.with(|s| s.borrow().get(&handle).map(|d| f(&d.image_secret))) } pub fn remove(handle: u32) -> bool {