- Created SelfFreezoneClient in Self components
- Wraps SDK FreezoneScriptClient for Self-specific operations
- Implements send_verification_email method
- Uses Rhai script template for email verification
- Includes template variable substitution
- Added serde-wasm-bindgen dependency
Usage:
let client = SelfFreezoneClient::builder()
.supervisor_url("http://localhost:8080")
.secret("my-secret")
.build()?;
client.send_verification_email(
"user@example.com",
"123456",
"https://verify.com/abc"
).await?;
571 lines
18 KiB
Markdown
571 lines
18 KiB
Markdown
# Vault System Documentation
|
|
|
|
## Overview
|
|
|
|
The Self vault system provides secure storage and management of multiple encrypted cryptographic keys. It enables users to maintain multiple digital identities, each with its own key pair, while ensuring all private keys remain encrypted and under user control.
|
|
|
|
## Architecture
|
|
|
|
### Core Components
|
|
|
|
```mermaid
|
|
graph TB
|
|
VM[Vault Manager] --> V[Vault]
|
|
VM --> VE[Vault Entry]
|
|
V --> LS[Local Storage]
|
|
VE --> EPK[Encrypted Private Key]
|
|
VE --> MD[Metadata]
|
|
|
|
subgraph "Encryption Layer"
|
|
EPK --> AES[AES-256-GCM]
|
|
AES --> PBKDF2[PBKDF2 Key Derivation]
|
|
end
|
|
|
|
subgraph "Storage Layer"
|
|
LS --> JSON[JSON Format]
|
|
JSON --> B64[Base64 Encoding]
|
|
end
|
|
```
|
|
|
|
### Data Structures
|
|
|
|
#### Vault Entry
|
|
```rust
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VaultEntry {
|
|
pub id: String, // Unique identifier (UUID)
|
|
pub name: String, // User-friendly name
|
|
pub email: String, // Associated email address
|
|
pub public_key: String, // Hex-encoded public key
|
|
pub encrypted_private_key: EncryptedPrivateKey, // Encrypted private key
|
|
pub created_at: String, // ISO 8601 timestamp
|
|
}
|
|
```
|
|
|
|
#### Vault Configuration
|
|
```rust
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VaultConfig {
|
|
pub app_name: String, // Application identifier
|
|
pub storage_key: String, // LocalStorage key prefix
|
|
pub auto_lock_timeout: u32, // Auto-lock timeout in minutes
|
|
}
|
|
```
|
|
|
|
## Vault Operations
|
|
|
|
### Creating a New Vault Entry
|
|
|
|
```rust
|
|
impl Vault {
|
|
pub fn store_keypair(
|
|
name: &str,
|
|
email: &str,
|
|
keypair: &KeyPair,
|
|
password: &str,
|
|
) -> Result<String, VaultError> {
|
|
// Generate unique ID
|
|
let id = Uuid::new_v4().to_string();
|
|
|
|
// Encrypt private key
|
|
let encrypted_private_key = encrypt_private_key(&keypair.private_key, password)?;
|
|
|
|
// Create vault entry
|
|
let entry = VaultEntry {
|
|
id: id.clone(),
|
|
name: name.to_string(),
|
|
email: email.to_string(),
|
|
public_key: keypair.public_key.clone(),
|
|
encrypted_private_key,
|
|
created_at: Utc::now().to_rfc3339(),
|
|
};
|
|
|
|
// Store in vault
|
|
self.add_entry(entry)?;
|
|
Ok(id)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Retrieving Keys from Vault
|
|
|
|
```rust
|
|
impl Vault {
|
|
pub fn retrieve_keypair(password: &str) -> Result<(String, String), VaultError> {
|
|
// Get primary identity from storage
|
|
let storage = web_sys::window()
|
|
.and_then(|w| w.local_storage().ok().flatten())
|
|
.ok_or(VaultError::StorageNotAvailable)?;
|
|
|
|
let vault_data = storage
|
|
.get_item("self_vault")
|
|
.map_err(|_| VaultError::StorageError)?
|
|
.ok_or(VaultError::NoKeysStored)?;
|
|
|
|
let vault: VaultData = serde_json::from_str(&vault_data)
|
|
.map_err(|_| VaultError::InvalidVaultFormat)?;
|
|
|
|
// Find primary identity
|
|
let entry = vault.entries
|
|
.values()
|
|
.next()
|
|
.ok_or(VaultError::NoKeysStored)?;
|
|
|
|
// Decrypt private key
|
|
let private_key = decrypt_private_key(&entry.encrypted_private_key, password)?;
|
|
|
|
Ok((private_key, entry.public_key.clone()))
|
|
}
|
|
}
|
|
```
|
|
|
|
### Listing Vault Entries
|
|
|
|
```rust
|
|
impl VaultManager {
|
|
pub fn list_identities(&self) -> Vec<IdentitySummary> {
|
|
self.vault_data
|
|
.entries
|
|
.values()
|
|
.map(|entry| IdentitySummary {
|
|
id: entry.id.clone(),
|
|
name: entry.name.clone(),
|
|
email: entry.email.clone(),
|
|
public_key: entry.public_key.clone(),
|
|
created_at: entry.created_at.clone(),
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
```
|
|
|
|
## Vault Manager Component
|
|
|
|
### Component State
|
|
|
|
```rust
|
|
pub struct VaultManager {
|
|
vault_data: VaultData,
|
|
selected_identity: Option<String>,
|
|
password_input: String,
|
|
show_password_input: bool,
|
|
loading: bool,
|
|
error_message: Option<String>,
|
|
show_create_form: bool,
|
|
new_identity_name: String,
|
|
new_identity_email: String,
|
|
}
|
|
```
|
|
|
|
### Key Management Operations
|
|
|
|
#### Adding New Identity
|
|
```rust
|
|
fn handle_create_identity(&mut self, ctx: &Context<Self>) {
|
|
if self.new_identity_name.trim().is_empty() ||
|
|
self.new_identity_email.trim().is_empty() {
|
|
self.error_message = Some("Name and email are required".to_string());
|
|
return;
|
|
}
|
|
|
|
let name = self.new_identity_name.clone();
|
|
let email = self.new_identity_email.clone();
|
|
let password = self.password_input.clone();
|
|
let link = ctx.link().clone();
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
match generate_keypair() {
|
|
Ok(keypair) => {
|
|
match Vault::store_keypair(&name, &email, &keypair, &password) {
|
|
Ok(id) => {
|
|
link.send_message(VaultMsg::IdentityCreated(id));
|
|
}
|
|
Err(e) => {
|
|
link.send_message(VaultMsg::Error(format!("Failed to store identity: {}", e)));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
link.send_message(VaultMsg::Error(format!("Failed to generate keys: {}", e)));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Selecting Identity
|
|
```rust
|
|
fn handle_select_identity(&mut self, identity_id: String, ctx: &Context<Self>) {
|
|
if self.password_input.trim().is_empty() {
|
|
self.error_message = Some("Password required to access identity".to_string());
|
|
return;
|
|
}
|
|
|
|
self.loading = true;
|
|
self.selected_identity = Some(identity_id.clone());
|
|
|
|
let password = self.password_input.clone();
|
|
let link = ctx.link().clone();
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
match Vault::decrypt_identity(&identity_id, &password) {
|
|
Ok(keypair) => {
|
|
link.send_message(VaultMsg::IdentitySelected(keypair));
|
|
}
|
|
Err(e) => {
|
|
link.send_message(VaultMsg::Error(format!("Failed to decrypt identity: {}", e)));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
## Storage Format
|
|
|
|
### Vault Data Structure
|
|
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"created_at": "2024-01-01T00:00:00Z",
|
|
"entries": {
|
|
"uuid-1": {
|
|
"id": "uuid-1",
|
|
"name": "Primary Identity",
|
|
"email": "user@example.com",
|
|
"public_key": "04a1b2c3d4e5f6...",
|
|
"encrypted_private_key": {
|
|
"encrypted_data": "base64-ciphertext",
|
|
"nonce": "base64-nonce",
|
|
"salt": "base64-salt"
|
|
},
|
|
"created_at": "2024-01-01T00:00:00Z"
|
|
},
|
|
"uuid-2": {
|
|
"id": "uuid-2",
|
|
"name": "Work Identity",
|
|
"email": "work@company.com",
|
|
"public_key": "04b2c3d4e5f6a1...",
|
|
"encrypted_private_key": {
|
|
"encrypted_data": "base64-ciphertext-2",
|
|
"nonce": "base64-nonce-2",
|
|
"salt": "base64-salt-2"
|
|
},
|
|
"created_at": "2024-01-02T00:00:00Z"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Storage Keys
|
|
|
|
- **Primary Vault**: `self_vault` - Main vault storage
|
|
- **Active Identity**: `self_active_identity` - Currently selected identity ID
|
|
- **Session Data**: `self_session` - Temporary session information
|
|
|
|
## Security Model
|
|
|
|
### Encryption Strategy
|
|
|
|
1. **Individual Key Encryption**: Each private key encrypted separately
|
|
2. **Unique Salts**: Each key uses its own random salt
|
|
3. **Password-Based Access**: Same password can decrypt all keys in vault
|
|
4. **No Master Key**: No single key encrypts the entire vault
|
|
|
|
### Password Management
|
|
|
|
```rust
|
|
impl VaultManager {
|
|
fn validate_password(&self, password: &str) -> Result<(), VaultError> {
|
|
if password.len() < 8 {
|
|
return Err(VaultError::WeakPassword("Password must be at least 8 characters".to_string()));
|
|
}
|
|
|
|
// Additional password strength checks
|
|
let has_upper = password.chars().any(|c| c.is_uppercase());
|
|
let has_lower = password.chars().any(|c| c.is_lowercase());
|
|
let has_digit = password.chars().any(|c| c.is_numeric());
|
|
|
|
if !has_upper || !has_lower || !has_digit {
|
|
return Err(VaultError::WeakPassword(
|
|
"Password must contain uppercase, lowercase, and numeric characters".to_string()
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
### Access Control
|
|
|
|
```rust
|
|
pub enum VaultAccess {
|
|
ReadOnly, // Can view public information only
|
|
Decrypt, // Can decrypt and use private keys
|
|
Manage, // Can add/remove identities
|
|
}
|
|
|
|
impl VaultManager {
|
|
pub fn check_access(&self, required_access: VaultAccess) -> bool {
|
|
match required_access {
|
|
VaultAccess::ReadOnly => true,
|
|
VaultAccess::Decrypt => self.is_unlocked(),
|
|
VaultAccess::Manage => self.is_unlocked() && self.has_management_privileges(),
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Vault Error Types
|
|
|
|
```rust
|
|
#[derive(Debug, Clone)]
|
|
pub enum VaultError {
|
|
StorageNotAvailable,
|
|
StorageError,
|
|
NoKeysStored,
|
|
InvalidVaultFormat,
|
|
EncryptionFailed(String),
|
|
DecryptionFailed(String),
|
|
WeakPassword(String),
|
|
IdentityNotFound(String),
|
|
DuplicateIdentity(String),
|
|
InvalidKeyFormat,
|
|
}
|
|
|
|
impl fmt::Display for VaultError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
VaultError::StorageNotAvailable => write!(f, "Browser storage not available"),
|
|
VaultError::StorageError => write!(f, "Failed to access storage"),
|
|
VaultError::NoKeysStored => write!(f, "No keys found in vault"),
|
|
VaultError::InvalidVaultFormat => write!(f, "Invalid vault data format"),
|
|
VaultError::EncryptionFailed(msg) => write!(f, "Encryption failed: {}", msg),
|
|
VaultError::DecryptionFailed(msg) => write!(f, "Decryption failed: {}", msg),
|
|
VaultError::WeakPassword(msg) => write!(f, "Weak password: {}", msg),
|
|
VaultError::IdentityNotFound(id) => write!(f, "Identity not found: {}", id),
|
|
VaultError::DuplicateIdentity(email) => write!(f, "Identity already exists: {}", email),
|
|
VaultError::InvalidKeyFormat => write!(f, "Invalid key format"),
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## User Interface
|
|
|
|
### Vault Manager UI Components
|
|
|
|
#### Identity List
|
|
```rust
|
|
fn render_identity_list(&self, ctx: &Context<Self>) -> Html {
|
|
let identities = self.list_identities();
|
|
|
|
html! {
|
|
<div class="identity-list">
|
|
<h5>{"Stored Identities"}</h5>
|
|
{for identities.iter().map(|identity| {
|
|
let identity_id = identity.id.clone();
|
|
html! {
|
|
<div class="identity-card" key={identity.id.clone()}>
|
|
<div class="identity-info">
|
|
<h6>{&identity.name}</h6>
|
|
<p class="text-muted">{&identity.email}</p>
|
|
<small class="text-muted">
|
|
{"Created: "}{&identity.created_at}
|
|
</small>
|
|
</div>
|
|
<div class="identity-actions">
|
|
<button class="btn btn-primary btn-sm"
|
|
onclick={ctx.link().callback(move |_| {
|
|
VaultMsg::SelectIdentity(identity_id.clone())
|
|
})}>
|
|
{"Select"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
})}
|
|
</div>
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Create Identity Form
|
|
```rust
|
|
fn render_create_form(&self, ctx: &Context<Self>) -> Html {
|
|
html! {
|
|
<div class="create-identity-form">
|
|
<h5>{"Create New Identity"}</h5>
|
|
<div class="mb-3">
|
|
<label class="form-label">{"Name"}</label>
|
|
<input type="text" class="form-control"
|
|
value={self.new_identity_name.clone()}
|
|
oninput={ctx.link().callback(|e: InputEvent| {
|
|
let input: HtmlInputElement = e.target_unchecked_into();
|
|
VaultMsg::UpdateNewIdentityName(input.value())
|
|
})} />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{"Email"}</label>
|
|
<input type="email" class="form-control"
|
|
value={self.new_identity_email.clone()}
|
|
oninput={ctx.link().callback(|e: InputEvent| {
|
|
let input: HtmlInputElement = e.target_unchecked_into();
|
|
VaultMsg::UpdateNewIdentityEmail(input.value())
|
|
})} />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{"Password"}</label>
|
|
<input type="password" class="form-control"
|
|
value={self.password_input.clone()}
|
|
oninput={ctx.link().callback(|e: InputEvent| {
|
|
let input: HtmlInputElement = e.target_unchecked_into();
|
|
VaultMsg::UpdatePassword(input.value())
|
|
})} />
|
|
</div>
|
|
<button class="btn btn-success"
|
|
onclick={ctx.link().callback(|_| VaultMsg::CreateIdentity)}
|
|
disabled={self.loading}>
|
|
{if self.loading { "Creating..." } else { "Create Identity" }}
|
|
</button>
|
|
</div>
|
|
}
|
|
}
|
|
```
|
|
|
|
## Integration with Other Components
|
|
|
|
### Registration Integration
|
|
|
|
```rust
|
|
// Auto-store generated keys during registration
|
|
impl Registration {
|
|
fn complete_registration(&mut self, ctx: &Context<Self>) {
|
|
if let (Some(keypair), Some(password)) = (&self.generated_keypair, &self.password) {
|
|
// Store in vault automatically
|
|
match Vault::store_keypair(
|
|
&self.name,
|
|
&self.email,
|
|
keypair,
|
|
password
|
|
) {
|
|
Ok(id) => {
|
|
web_sys::console::log_1(&format!("Identity stored in vault: {}", id).into());
|
|
}
|
|
Err(e) => {
|
|
web_sys::console::log_1(&format!("Failed to store in vault: {}", e).into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Login Integration
|
|
|
|
```rust
|
|
// Select identity from vault for login
|
|
impl Login {
|
|
fn load_from_vault(&mut self, identity_id: &str, password: &str) -> Result<(), String> {
|
|
match Vault::decrypt_identity(identity_id, password) {
|
|
Ok(keypair) => {
|
|
self.current_keypair = Some(keypair);
|
|
Ok(())
|
|
}
|
|
Err(e) => Err(format!("Failed to load identity: {}", e))
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Backup and Recovery
|
|
|
|
### Export Functionality
|
|
|
|
```rust
|
|
impl VaultManager {
|
|
pub fn export_vault(&self, password: &str) -> Result<String, VaultError> {
|
|
// Verify password can decrypt at least one identity
|
|
self.verify_vault_password(password)?;
|
|
|
|
// Export vault data (still encrypted)
|
|
let export_data = ExportData {
|
|
version: "1.0".to_string(),
|
|
exported_at: Utc::now().to_rfc3339(),
|
|
vault: self.vault_data.clone(),
|
|
};
|
|
|
|
serde_json::to_string_pretty(&export_data)
|
|
.map_err(|_| VaultError::StorageError)
|
|
}
|
|
|
|
pub fn import_vault(&mut self, import_data: &str, password: &str) -> Result<(), VaultError> {
|
|
let export_data: ExportData = serde_json::from_str(import_data)
|
|
.map_err(|_| VaultError::InvalidVaultFormat)?;
|
|
|
|
// Verify password can decrypt imported identities
|
|
for entry in export_data.vault.entries.values() {
|
|
decrypt_private_key(&entry.encrypted_private_key, password)?;
|
|
}
|
|
|
|
// Merge with existing vault
|
|
for (id, entry) in export_data.vault.entries {
|
|
self.vault_data.entries.insert(id, entry);
|
|
}
|
|
|
|
self.save_vault()
|
|
}
|
|
}
|
|
```
|
|
|
|
### Recovery Options
|
|
|
|
1. **Password Recovery**: Not possible - passwords are not stored
|
|
2. **Vault Export**: Users must export vault data regularly
|
|
3. **Individual Key Backup**: Each private key can be backed up separately
|
|
4. **Seed Phrase**: Future enhancement for deterministic key generation
|
|
|
|
## Performance Considerations
|
|
|
|
### Optimization Strategies
|
|
|
|
1. **Lazy Loading**: Load vault data only when needed
|
|
2. **Caching**: Cache decrypted keys in memory during session
|
|
3. **Batch Operations**: Group multiple vault operations
|
|
4. **Background Sync**: Sync vault changes in background
|
|
|
|
### Memory Management
|
|
|
|
```rust
|
|
impl Drop for VaultManager {
|
|
fn drop(&mut self) {
|
|
// Clear sensitive data from memory
|
|
self.password_input.zeroize();
|
|
if let Some(ref mut keypair) = self.cached_keypair {
|
|
keypair.private_key.zeroize();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
### Planned Features
|
|
|
|
1. **Hierarchical Deterministic Keys**: BIP32-style key derivation
|
|
2. **Hardware Token Integration**: WebAuthn support
|
|
3. **Vault Synchronization**: Cross-device vault sync
|
|
4. **Biometric Authentication**: WebAuthn biometric support
|
|
5. **Key Rotation**: Automatic key rotation policies
|
|
6. **Audit Trail**: Comprehensive logging of vault operations
|
|
|
|
### Advanced Security Features
|
|
|
|
1. **Multi-Factor Authentication**: Additional authentication factors
|
|
2. **Time-Based Access**: Temporary key access permissions
|
|
3. **Geolocation Restrictions**: Location-based access controls
|
|
4. **Device Binding**: Tie vault access to specific devices
|