Files
self/docs/vault-system.md
Timur Gordon f970f3fb58 Add SelfFreezoneClient wrapper for Self components
- 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?;
2025-11-03 16:16:18 +01:00

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