fix: zeroize for secret keys in db operations

This commit is contained in:
kaichaosun 2026-03-02 11:38:44 +08:00
parent 0e62c44b7e
commit c3e5f361bb
No known key found for this signature in database
GPG Key ID: 223E0F992F4F03BF
2 changed files with 31 additions and 11 deletions

View File

@ -64,6 +64,7 @@ pub fn create_context(name: repr_c::String) -> repr_c::Box<ContextHandle> {
/// # Parameters /// # Parameters
/// - name: Friendly name for the identity (used if creating new identity) /// - name: Friendly name for the identity (used if creating new identity)
/// - db_path: Path to the SQLite database file /// - db_path: Path to the SQLite database file
/// - db_secret: Secret key for encrypting the database
/// ///
/// # Returns /// # Returns
/// CResult with context handle on success, or error string on failure. /// CResult with context handle on success, or error string on failure.
@ -73,8 +74,12 @@ pub fn create_context(name: repr_c::String) -> repr_c::Box<ContextHandle> {
pub fn create_context_with_storage( pub fn create_context_with_storage(
name: repr_c::String, name: repr_c::String,
db_path: repr_c::String, db_path: repr_c::String,
db_secret: repr_c::String,
) -> CResult<repr_c::Box<ContextHandle>, repr_c::String> { ) -> CResult<repr_c::Box<ContextHandle>, repr_c::String> {
let config = StorageConfig::File(db_path.to_string()); let config = StorageConfig::Encrypted {
path: db_path.to_string(),
key: db_secret.to_string(),
};
match Context::open(&*name, config) { match Context::open(&*name, config) {
Ok(ctx) => CResult { Ok(ctx) => CResult {
ok: Some(Box::new(ContextHandle(ctx)).into()), ok: Some(Box::new(ContextHandle(ctx)).into()),

View File

@ -1,6 +1,7 @@
//! Chat-specific storage implementation. //! Chat-specific storage implementation.
use storage::{RusqliteError, SqliteDb, StorageConfig, StorageError, params}; use storage::{RusqliteError, SqliteDb, StorageConfig, StorageError, params};
use zeroize::Zeroize;
use super::migrations; use super::migrations;
use super::types::IdentityRecord; use super::types::IdentityRecord;
@ -32,18 +33,24 @@ impl ChatStorage {
// ==================== Identity Operations ==================== // ==================== Identity Operations ====================
/// Saves the identity (secret key). /// Saves the identity (secret key).
///
/// Note: The secret key bytes are explicitly zeroized after use to minimize
/// the time sensitive data remains in stack memory.
pub fn save_identity(&mut self, identity: &Identity) -> Result<(), StorageError> { pub fn save_identity(&mut self, identity: &Identity) -> Result<(), StorageError> {
self.db.connection().execute( let mut secret_bytes = identity.secret().DANGER_to_bytes();
let result = self.db.connection().execute(
"INSERT OR REPLACE INTO identity (id, name, secret_key) VALUES (1, ?1, ?2)", "INSERT OR REPLACE INTO identity (id, name, secret_key) VALUES (1, ?1, ?2)",
params![ params![identity.get_name(), secret_bytes.as_slice()],
identity.get_name(), );
identity.secret().DANGER_to_bytes().as_slice() secret_bytes.zeroize();
], result?;
)?;
Ok(()) Ok(())
} }
/// Loads the identity if it exists. /// Loads the identity if it exists.
///
/// Note: Secret key bytes are zeroized after being copied into IdentityRecord,
/// which handles its own zeroization via ZeroizeOnDrop.
pub fn load_identity(&self) -> Result<Option<Identity>, StorageError> { pub fn load_identity(&self) -> Result<Option<Identity>, StorageError> {
let mut stmt = self let mut stmt = self
.db .db
@ -57,10 +64,18 @@ impl ChatStorage {
}); });
match result { match result {
Ok((name, secret_key)) => { Ok((name, mut secret_key_vec)) => {
let bytes: [u8; 32] = secret_key let bytes: Result<[u8; 32], _> = secret_key_vec.as_slice().try_into();
.try_into() let bytes = match bytes {
.map_err(|_| StorageError::InvalidData("Invalid secret key length".into()))?; Ok(b) => b,
Err(_) => {
secret_key_vec.zeroize();
return Err(StorageError::InvalidData(
"Invalid secret key length".into(),
));
}
};
secret_key_vec.zeroize();
let record = IdentityRecord { let record = IdentityRecord {
name, name,
secret_key: bytes, secret_key: bytes,