Merge branch 'main_browser_extension' of https://git.ourworld.tf/samehabouelsaad/sal-modular into main_browser_extension
This commit is contained in:
commit
536c077fbf
@ -11,8 +11,15 @@ use rand_core::{RngCore, OsRng as RandOsRng};
|
|||||||
pub mod kdf {
|
pub mod kdf {
|
||||||
use super::*;
|
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> {
|
pub fn derive_key_pbkdf2(password: &[u8], salt: &[u8], key_len: usize, iterations: u32) -> Vec<u8> {
|
||||||
let mut key = vec![0u8; key_len];
|
let mut key = vec![0u8; key_len];
|
||||||
|
@ -84,7 +84,7 @@ impl<S: KVStore> Vault<S> {
|
|||||||
debug!("salt: {:?}", salt);
|
debug!("salt: {:?}", salt);
|
||||||
// 2. Derive key
|
// 2. Derive key
|
||||||
// Always use PBKDF2 for key derivation
|
// 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());
|
debug!("derived key: {} bytes", key.len());
|
||||||
// 3. Prepare initial keyspace data
|
// 3. Prepare initial keyspace data
|
||||||
let keyspace_data = KeyspaceData { keypairs: vec![] };
|
let keyspace_data = KeyspaceData { keypairs: vec![] };
|
||||||
@ -107,7 +107,7 @@ impl<S: KVStore> Vault<S> {
|
|||||||
// 6. Compose metadata
|
// 6. Compose metadata
|
||||||
let metadata = KeyspaceMetadata {
|
let metadata = KeyspaceMetadata {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
salt: salt.try_into().unwrap_or([0u8; 16]),
|
salt: salt.clone().try_into().unwrap_or([0u8; 16]),
|
||||||
encrypted_blob,
|
encrypted_blob,
|
||||||
created_at: Some(crate::utils::now()),
|
created_at: Some(crate::utils::now()),
|
||||||
tags,
|
tags,
|
||||||
@ -125,6 +125,10 @@ impl<S: KVStore> Vault<S> {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
debug!("success");
|
debug!("success");
|
||||||
|
|
||||||
|
// 8. Create default keypair, passing the salt we already have
|
||||||
|
self.create_default_keypair(name, password, &salt).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +184,7 @@ impl<S: KVStore> Vault<S> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
// 2. Derive key
|
// 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());
|
debug!("derived key: {} bytes", key.len());
|
||||||
|
|
||||||
let ciphertext = &metadata.encrypted_blob;
|
let ciphertext = &metadata.encrypted_blob;
|
||||||
@ -213,6 +217,56 @@ impl<S: KVStore> Vault<S> {
|
|||||||
|
|
||||||
// --- Keypair Management APIs ---
|
// --- 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)
|
||||||
/// 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.
|
/// If key_type is None, defaults to Secp256k1.
|
||||||
@ -341,7 +395,7 @@ impl<S: KVStore> Vault<S> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
// 2. Derive key
|
// 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());
|
debug!("derived key: {} bytes", key.len());
|
||||||
// 3. Serialize plaintext
|
// 3. Serialize plaintext
|
||||||
let plaintext = match serde_json::to_vec(data) {
|
let plaintext = match serde_json::to_vec(data) {
|
||||||
@ -500,7 +554,7 @@ impl<S: KVStore> Vault<S> {
|
|||||||
hex::encode(&meta.salt)
|
hex::encode(&meta.salt)
|
||||||
);
|
);
|
||||||
// 2. Derive key
|
// 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
|
// 3. Generate nonce
|
||||||
let nonce = random_salt(12);
|
let nonce = random_salt(12);
|
||||||
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce));
|
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce));
|
||||||
@ -547,7 +601,7 @@ impl<S: KVStore> Vault<S> {
|
|||||||
hex::encode(&meta.salt)
|
hex::encode(&meta.salt)
|
||||||
);
|
);
|
||||||
// 2. Derive key
|
// 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
|
// 3. Extract nonce
|
||||||
let nonce = &ciphertext[..12];
|
let nonce = &ciphertext[..12];
|
||||||
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce));
|
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce));
|
||||||
|
@ -13,6 +13,7 @@ pub fn register_rhai_api<S: kvstore::traits::KVStore + Send + Sync + Clone + 'st
|
|||||||
) {
|
) {
|
||||||
engine.register_type::<RhaiSessionManager<S>>();
|
engine.register_type::<RhaiSessionManager<S>>();
|
||||||
engine.register_fn("select_keypair", RhaiSessionManager::<S>::select_keypair);
|
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);
|
engine.register_fn("sign", RhaiSessionManager::<S>::sign);
|
||||||
// No global constant registration: Rhai does not support this directly.
|
// No global constant registration: Rhai does not support this directly.
|
||||||
// Scripts should receive the session manager as a parameter or via module scope.
|
// 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
|
// Use Mutex for interior mutability, &self is sufficient
|
||||||
self.inner.lock().unwrap().select_keypair(&key_id).map_err(|e| format!("select_keypair error: {e}"))
|
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> {
|
pub fn sign(&self, message: rhai::Blob) -> Result<rhai::Blob, String> {
|
||||||
let sm = self.inner.lock().unwrap();
|
let sm = self.inner.lock().unwrap();
|
||||||
// Try to get the current keyspace name from session state if possible
|
// Try to get the current keyspace name from session state if possible
|
||||||
|
@ -125,6 +125,30 @@ impl<S: KVStore + Send + Sync> SessionManager<S> {
|
|||||||
self.unlocked_keyspace.is_some()
|
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> {
|
pub async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError> {
|
||||||
let (name, password, _) = self
|
let (name, password, _) = self
|
||||||
.unlocked_keyspace
|
.unlocked_keyspace
|
||||||
@ -283,6 +307,30 @@ impl<S: KVStore> SessionManager<S> {
|
|||||||
self.unlocked_keyspace.is_some()
|
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> {
|
pub async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError> {
|
||||||
let (name, password, _) = self
|
let (name, password, _) = self
|
||||||
.unlocked_keyspace
|
.unlocked_keyspace
|
||||||
|
Loading…
Reference in New Issue
Block a user