hana-vault

Crates.iohana-vault
lib.rshana-vault
version3.0.0
created_at2025-11-15 00:39:57.685693+00
updated_at2026-01-25 11:26:24.456596+00
descriptionA library used by the Hana SSH client for storing user data securely in encrypted SQLite.
homepagehttps://docs.hanassh.com/
repositoryhttps://github.com/hana-ssh/hana-vault
max_upload_size
id1933858
size98,975
Maciej Gomoła (MaciejkaG)

documentation

README

Hana Vault Format

A secure, production-ready Rust library for storing SSH credentials, hosts, and sensitive data using an encrypted SQLite vault format.

License: MIT Rust

Features

  • In-Memory SQLite Storage - Database lives in RAM and is never written to disk in plaintext
  • Binary Encryption - Export/import vaults as encrypted bytes (perfect for network transmission)
  • Autonomous Migrations - Schema versioning with automatic migration system
  • Multiple Auth Types - Support for username/password, RSA, OpenSSH, Ed25519, ECDSA, and certificate-based authentication
  • Host Management - Store hosts with startup commands, environment variables, and custom encodings
  • Industry-Standard Crypto - AES-256-GCM symmetric encryption
  • Key-Based API - Supply your own 256-bit key (derive from password in your application)
  • Zero Plaintext - Raw SQLite database never exposed - only encrypted formats allowed
  • Memory Safety - Sensitive data automatically zeroized on drop

Security Features

Multi-Layer Security

  1. In-Memory SQLite Database

    • Database exists only in RAM, never on disk
    • Isolated from filesystem exposure
    • Automatic cleanup on process termination
  2. Binary Format Encryption

    • AES-256-GCM for authenticated encryption
    • Key-based API (derive keys using Argon2id in your application)
    • SHA-256 checksums for integrity verification
    • Random nonce for each encryption operation
  3. Security Best Practices

    • No plaintext SQLite ever written to disk
    • In-memory database only
    • Zeroization of sensitive data
    • Foreign key constraints for data integrity
    • Prepared statements to prevent SQL injection

Quick Start

use hana_vault::{Vault, Host, Credential, SecretKey};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate or derive a 256-bit key
    // In production, derive from password using Argon2id
    let key = SecretKey::random();

    // Create a new vault
    let vault = Vault::new(key.clone())?;

    // Add a host with environment variables
    let mut host = Host::new("Production Server".to_string(), "prod.example.com".to_string(), 22);
    host.add_env_var("NODE_ENV".to_string(), "production".to_string());
    host = host.with_startup_command("source ~/.profile".to_string());
    host = host.with_username("deploy".to_string());

    vault.add_host(&host)?;

    // Add SSH key credentials
    let cred = Credential::new_ssh_key(
        "Deploy Key".to_string(),
        hana_vault::CredentialType::OpenSsh,
        "-----BEGIN OPENSSH PRIVATE KEY-----\n...".to_string(),
        Some("ssh-ed25519 AAAAC3...".to_string()),
        None, // Optional passphrase
    );

    vault.add_credential(&cred)?;

    // Link credential to host
    vault.link_credential_to_host(host.id, cred.id, true)?;

    // Export as encrypted bytes (e.g., to save to file or send to server)
    let encrypted_bytes = vault.export_to_bytes()?;
    std::fs::write("vault.hev", &encrypted_bytes)?;

    Ok(())
}

Key Derivation

This library accepts a raw 256-bit key. In your application, derive it from a user password using Argon2id:

use argon2::{Argon2, Params, Version};
use hana_vault::{SecretKey, KEY_SIZE};

fn derive_key(password: &str, salt: &[u8]) -> SecretKey {
    let params = Params::new(65536, 3, 4, Some(KEY_SIZE)).unwrap();
    let argon2 = Argon2::new(argon2::Algorithm::Argon2id, Version::V0x13, params);
    
    let mut key = [0u8; KEY_SIZE];
    argon2.hash_password_into(password.as_bytes(), salt, &mut key).unwrap();
    
    SecretKey::from_bytes(&key)
}

Usage Examples

Loading Vaults

use hana_vault::{Vault, SecretKey};

// Load from encrypted file
let encrypted_bytes = std::fs::read("vault.hev")?;
let key = derive_key("password", &salt); // Your key derivation
let vault = Vault::load_from_bytes(&encrypted_bytes, key)?;

Reading Vault ID

You can read the unique identifier of a vault without decrypting it (useful for system keyrings):

use hana_vault::Vault;

let encrypted_bytes = std::fs::read("vault.hev")?;
let vault_id = Vault::get_id_from_bytes(&encrypted_bytes)?;
println!("Vault ID: {}", vault_id);

Managing Hosts

use hana_vault::Host;

// Create a host
let mut host = Host::new("Web Server".to_string(), "web.example.com".to_string(), 22);

// Add startup command
host = host.with_startup_command("cd /var/www && source env.sh".to_string());

// Set custom encoding
host = host.with_encoding("UTF-8".to_string());

// Add environment variables
host.add_env_var("PATH".to_string(), "/usr/local/bin:/usr/bin".to_string());
host.add_env_var("PORT".to_string(), "3000".to_string());

vault.add_host(&host)?;

// List all hosts
let hosts = vault.list_hosts()?;

// Get specific host
let host = vault.get_host(host_id)?;

// Update host
host.port = 2222;
vault.update_host(&host)?;

// Delete host
vault.delete_host(host_id)?;

Managing Credentials

use hana_vault::{Credential, CredentialType};

// Username/Password credentials
let cred = Credential::new_username_password(
    "Admin Login".to_string(),
    "admin".to_string(),
    "super_secret_password".to_string(),
);
vault.add_credential(&cred)?;

// RSA key credentials
let rsa_cred = Credential::new_ssh_key(
    "RSA Key".to_string(),
    CredentialType::Rsa,
    "-----BEGIN RSA PRIVATE KEY-----\n...".to_string(),
    Some("ssh-rsa AAAAB3...".to_string()),
    Some("key_passphrase".to_string()),
);
vault.add_credential(&rsa_cred)?;

// Ed25519 key credentials
let ed25519_cred = Credential::new_ssh_key(
    "Ed25519 Key".to_string(),
    CredentialType::Ed25519,
    "-----BEGIN OPENSSH PRIVATE KEY-----\n...".to_string(),
    Some("ssh-ed25519 AAAAC3...".to_string()),
    None,
);
vault.add_credential(&ed25519_cred)?;

// List all credentials
let credentials = vault.list_credentials()?;

// Update credential
cred.name = "Updated Name".to_string();
vault.update_credential(&cred)?;

// Delete credential
vault.delete_credential(cred_id)?;

Linking Credentials to Hosts

// Link a credential to a host (set as default)
vault.link_credential_to_host(host_id, credential_id, true)?;

// Link another credential to the same host (not default)
vault.link_credential_to_host(host_id, another_credential_id, false)?;

// Get all credentials for a host
let host_creds = vault.get_host_credentials(host_id)?;
for (credential, is_default) in host_creds {
    println!("{}: {} (default: {})", credential.id, credential.name, is_default);
}

// Unlink credential from host
vault.unlink_credential_from_host(host_id, credential_id)?;

Database Schema

The library uses an SQLite database with the following schema:

  • hosts - Host configurations (hostname, port, startup commands, encoding, etc.)
  • host_env_vars - Environment variables for hosts
  • credentials - Authentication credentials (all types)
  • host_credentials - Many-to-many relationship between hosts and credentials
  • schema_version - Automatic migration tracking

Versioning & Migrations

The library includes an autonomous migration system that:

  • Automatically detects schema version on vault load
  • Runs pending migrations seamlessly
  • Ensures backward compatibility
  • Tracks applied migrations

Migrations run automatically when you load a vault, ensuring it's always at the latest schema version.

KDF Compatibility

Vaults encrypted with one key-derivation configuration are not guaranteed to be decryptable by builds that change the KDF parameters/algorithm. If you need to support older vault files, keep the previous KDF available for decryption and migrate by re-exporting.

Testing

Run the comprehensive test suite:

cargo test

Run tests with output:

cargo test -- --nocapture

API Reference

Core Types

  • Vault - Main vault structure for managing encrypted data

    • new(key) - Create new vault with encryption key
    • load_from_bytes(bytes, key) - Load from encrypted bytes
    • export_to_bytes() - Export as encrypted bytes
    • get_id_from_bytes(bytes) - Get vault ID without decrypting
  • SecretKey - 256-bit encryption key

    • random() - Generate a random key
    • from_bytes(bytes) - Create from raw bytes (e.g., after KDF)
  • Host - Host configuration

    • new(name, hostname, port) - Create new host
    • with_startup_command(cmd) - Set startup command
    • with_encoding(encoding) - Set custom encoding
    • add_env_var(key, value) - Add environment variable
  • Credential - Authentication credential

    • new_username_password(name, username, password) - Create password credential
    • new_ssh_key(name, type, private_key, public_key, passphrase) - Create SSH key credential
  • CredentialType - Enum of supported authentication types

    • UsernamePassword
    • Rsa
    • OpenSsh
    • Ed25519
    • Ecdsa
    • Certificate
    • Custom

Error Handling

All operations return Result<T, VaultError>:

use hana_vault::{Vault, VaultError, SecretKey};

let bytes = std::fs::read("vault.hev")?;
let key = SecretKey::random(); // Your derived key
match Vault::load_from_bytes(&bytes, key) {
    Ok(vault) => println!("Vault loaded successfully"),
    Err(VaultError::InvalidKey) => eprintln!("Wrong key"),
    Err(VaultError::CorruptedData(msg)) => eprintln!("Data corruption: {}", msg),
    Err(e) => eprintln!("Error: {}", e),
}

File Format

Encrypted vault files (.hev) have the following structure:

┌─────────────────────────────────────────┐
│ Header (68 bytes, unencrypted)          │
│  ├─ Magic bytes (8 bytes): "HANAVLT1"   │
│  ├─ Version (4 bytes): u32              │
│  ├─ UID (16 bytes): UUID v4             │
│  ├─ Checksum (32 bytes): SHA-256        │
│  └─ Data size (8 bytes): u64            │
├─────────────────────────────────────────┤
│ Encrypted Data                          │
│  ├─ Nonce (12 bytes)                    │
│  └─ Ciphertext (variable)               │
│      └─ Encrypted SQLite database       │
└─────────────────────────────────────────┘

Contributing

Contributions are welcome! Please ensure:

  • All tests pass (cargo test)
  • Code follows Rust conventions (cargo fmt, cargo clippy)
  • Security implications are considered
  • Documentation is updated

License

This project is licensed under the MIT License - see the LICENSE file for details.

Commit count: 16

cargo fmt