Merge branch 'main_browser_extension' of https://git.ourworld.tf/samehabouelsaad/sal-modular into main_browser_extension

This commit is contained in:
zaelgohary 2025-05-29 08:57:28 +03:00
commit 536c077fbf
4 changed files with 123 additions and 8 deletions

View File

@ -11,8 +11,15 @@ use rand_core::{RngCore, OsRng as RandOsRng};
pub mod kdf {
use super::*;
/// Standard parameters for keyspace key derivation
pub const KEYSPACE_KEY_LENGTH: usize = 32;
pub const KEYSPACE_KEY_ITERATIONS: u32 = 10_000;
/// Derive a symmetric key for keyspace operations using standard parameters
/// Always uses PBKDF2 with SHA-256, 32 bytes output, and 10,000 iterations
pub fn keyspace_key(password: &[u8], salt: &[u8]) -> Vec<u8> {
derive_key_pbkdf2(password, salt, KEYSPACE_KEY_LENGTH, KEYSPACE_KEY_ITERATIONS)
}
pub fn derive_key_pbkdf2(password: &[u8], salt: &[u8], key_len: usize, iterations: u32) -> Vec<u8> {
let mut key = vec![0u8; key_len];

View File

@ -68,7 +68,7 @@ impl<S: KVStore> Vault<S> {
.storage
.get(name)
.await
.map_err(|e| VaultError::Storage(format!("{e:?}")))?
.map_err(|e| VaultError::Storage(format!("{e:?}")))?
.is_some()
{
debug!("keyspace '{}' already exists", name);
@ -84,7 +84,7 @@ impl<S: KVStore> Vault<S> {
debug!("salt: {:?}", salt);
// 2. Derive key
// Always use PBKDF2 for key derivation
let key = kdf::derive_key_pbkdf2(password, &salt, 32, 10_000);
let key = kdf::keyspace_key(password, &salt);
debug!("derived key: {} bytes", key.len());
// 3. Prepare initial keyspace data
let keyspace_data = KeyspaceData { keypairs: vec![] };
@ -107,7 +107,7 @@ impl<S: KVStore> Vault<S> {
// 6. Compose metadata
let metadata = KeyspaceMetadata {
name: name.to_string(),
salt: salt.try_into().unwrap_or([0u8; 16]),
salt: salt.clone().try_into().unwrap_or([0u8; 16]),
encrypted_blob,
created_at: Some(crate::utils::now()),
tags,
@ -125,6 +125,10 @@ impl<S: KVStore> Vault<S> {
.await
.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
debug!("success");
// 8. Create default keypair, passing the salt we already have
self.create_default_keypair(name, password, &salt).await?;
Ok(())
}
@ -180,7 +184,7 @@ impl<S: KVStore> Vault<S> {
));
}
// 2. Derive key
let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000);
let key = kdf::keyspace_key(password, &metadata.salt);
debug!("derived key: {} bytes", key.len());
let ciphertext = &metadata.encrypted_blob;
@ -213,6 +217,56 @@ impl<S: KVStore> Vault<S> {
// --- Keypair Management APIs ---
/// Create a default Ed25519 keypair for client identity
/// This keypair is deterministically generated from the password and salt
/// and will always be the first keypair in the keyspace
async fn create_default_keypair(
&mut self,
keyspace: &str,
password: &[u8],
salt: &[u8],
) -> Result<String, VaultError> {
// 1. Derive a deterministic seed using standard PBKDF2
let seed = kdf::keyspace_key(password, salt);
// 2. Generate Ed25519 keypair from the seed
use ed25519_dalek::{SigningKey, VerifyingKey};
// Use the seed to create a deterministic keypair
let signing = SigningKey::from_bytes(seed.as_slice().try_into().unwrap());
let verifying: VerifyingKey = (&signing).into();
let priv_bytes = signing.to_bytes().to_vec();
let pub_bytes = verifying.to_bytes().to_vec();
// Create an ID for the default keypair
let id = hex::encode(&pub_bytes);
// 3. Unlock the keyspace to get its data
let mut data = self.unlock_keyspace(keyspace, password).await?;
// 4. Add to keypairs (as the first entry)
let entry = KeyEntry {
id: id.clone(),
key_type: KeyType::Ed25519,
private_key: priv_bytes,
public_key: pub_bytes,
metadata: Some(KeyMetadata {
name: Some("Default Identity".to_string()),
created_at: Some(crate::utils::now()),
tags: Some(vec!["default".to_string(), "identity".to_string()]),
}),
};
// Ensure it's the first keypair by inserting at index 0
data.keypairs.insert(0, entry);
// 5. Re-encrypt and store
self.save_keyspace(keyspace, password, &data).await?;
Ok(id)
}
/// Add a new keypair to a keyspace (generates and stores a new keypair)
/// Add a new keypair to a keyspace (generates and stores a new keypair)
/// If key_type is None, defaults to Secp256k1.
@ -341,7 +395,7 @@ impl<S: KVStore> Vault<S> {
));
}
// 2. Derive key
let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000);
let key = kdf::keyspace_key(password, &metadata.salt);
debug!("derived key: {} bytes", key.len());
// 3. Serialize plaintext
let plaintext = match serde_json::to_vec(data) {
@ -500,7 +554,7 @@ impl<S: KVStore> Vault<S> {
hex::encode(&meta.salt)
);
// 2. Derive key
let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000);
let key = kdf::keyspace_key(password, &meta.salt);
// 3. Generate nonce
let nonce = random_salt(12);
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce));
@ -547,7 +601,7 @@ impl<S: KVStore> Vault<S> {
hex::encode(&meta.salt)
);
// 2. Derive key
let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000);
let key = kdf::keyspace_key(password, &meta.salt);
// 3. Extract nonce
let nonce = &ciphertext[..12];
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce));

View File

@ -13,6 +13,7 @@ pub fn register_rhai_api<S: kvstore::traits::KVStore + Send + Sync + Clone + 'st
) {
engine.register_type::<RhaiSessionManager<S>>();
engine.register_fn("select_keypair", RhaiSessionManager::<S>::select_keypair);
engine.register_fn("select_default_keypair", RhaiSessionManager::<S>::select_default_keypair);
engine.register_fn("sign", RhaiSessionManager::<S>::sign);
// No global constant registration: Rhai does not support this directly.
// Scripts should receive the session manager as a parameter or via module scope.
@ -36,6 +37,11 @@ impl<S: kvstore::traits::KVStore + Send + Sync + Clone + 'static> RhaiSessionMan
// Use Mutex for interior mutability, &self is sufficient
self.inner.lock().unwrap().select_keypair(&key_id).map_err(|e| format!("select_keypair error: {e}"))
}
pub fn select_default_keypair(&self) -> Result<(), String> {
self.inner.lock().unwrap().select_default_keypair()
.map_err(|e| format!("select_default_keypair error: {e}"))
}
pub fn sign(&self, message: rhai::Blob) -> Result<rhai::Blob, String> {
let sm = self.inner.lock().unwrap();
// Try to get the current keyspace name from session state if possible

View File

@ -125,6 +125,30 @@ impl<S: KVStore + Send + Sync> SessionManager<S> {
self.unlocked_keyspace.is_some()
}
/// Returns the default keypair (first keypair) for client identity, if any.
pub fn default_keypair(&self) -> Option<&KeyEntry> {
self.current_keyspace()
.and_then(|ks| ks.keypairs.first())
}
/// Selects the default keypair (first keypair) as the current keypair.
pub fn select_default_keypair(&mut self) -> Result<(), VaultError> {
let default_id = self
.default_keypair()
.map(|k| k.id.clone())
.ok_or_else(|| VaultError::Crypto("No default keypair found".to_string()))?;
self.select_keypair(&default_id)
}
/// Returns true if the current keypair is the default keypair (first keypair).
pub fn is_default_keypair_selected(&self) -> bool {
match (self.current_keypair(), self.default_keypair()) {
(Some(current), Some(default)) => current.id == default.id,
_ => false,
}
}
pub async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError> {
let (name, password, _) = self
.unlocked_keyspace
@ -283,6 +307,30 @@ impl<S: KVStore> SessionManager<S> {
self.unlocked_keyspace.is_some()
}
/// Returns the default keypair (first keypair) for client identity, if any.
pub fn default_keypair(&self) -> Option<&KeyEntry> {
self.current_keyspace()
.and_then(|ks| ks.keypairs.first())
}
/// Selects the default keypair (first keypair) as the current keypair.
pub fn select_default_keypair(&mut self) -> Result<(), VaultError> {
let default_id = self
.default_keypair()
.map(|k| k.id.clone())
.ok_or_else(|| VaultError::Crypto("No default keypair found".to_string()))?;
self.select_keypair(&default_id)
}
/// Returns true if the current keypair is the default keypair (first keypair).
pub fn is_default_keypair_selected(&self) -> bool {
match (self.current_keypair(), self.default_keypair()) {
(Some(current), Some(default)) => current.id == default.id,
_ => false,
}
}
pub async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError> {
let (name, password, _) = self
.unlocked_keyspace