#vault #sqlite #ssh #hana #hanassh

hana-vault

A library used by the Hana SSH client for storing user data securely in encrypted SQLite

6 stable releases

2.0.0 Jan 11, 2026
1.1.0 Jan 10, 2026
1.0.3 Jan 3, 2026
1.0.2 Dec 27, 2025
1.0.0 Nov 15, 2025

#407 in Cryptography

MIT license

77KB
1.5K SLoC

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 with Argon2id key derivation
  • 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
  • Argon2id for password-based key derivation (defaults: 64 MiB memory, 3 iterations, parallelism 4)
  • SHA-256 checksums for integrity verification
  • Random salt and nonce for each encryption operation
  1. 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};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new vault
    let vault = Vault::new("secure_password")?;

    // 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(())
}

Usage Examples

Loading Vaults

use hana_vault::Vault;
use std::path::Path;

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

// Load from encrypted bytes (e.g., received from server)
let vault = Vault::load_from_bytes(&encrypted_bytes, "password")?;

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(name, password) - Create new vault
    • load_from_bytes(bytes, password) - Load from encrypted bytes
    • export_to_bytes() - Export as encrypted bytes
  • VaultStorage - Trait for implementing custom storage backends

    • fetch(password) - Fetch and load vault
    • save(vault) - Save vault
    • delete() - Delete vault
  • 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};

let bytes = std::fs::read("vault.hev")?;
match Vault::load_from_bytes(&bytes) {
    Ok(vault) => println!("Vault loaded successfully"),
    Err(VaultError::InvalidPassword) => eprintln!("Wrong password"),
    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                          │
│  ├─ Salt (32 bytes)                     │
│  ├─ 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.

Dependencies

~30MB
~559K SLoC