Skip to content

Latest commit

Β 

History

History
765 lines (572 loc) Β· 27.9 KB

File metadata and controls

765 lines (572 loc) Β· 27.9 KB

πŸ” Meow-Encoder Crypto Core

A comprehensive cryptographic library for Meow-Encode providing:

  • AEAD wrappers with enforced safety properties (type system + tests; Verus proofs are specification stubs, not yet machine-checked)
  • Hardware security via HSM/PKCS#11, YubiKey PIV/FIDO2, and TPM 2.0
  • Pure Rust crypto stack with X25519, Argon2id, HKDF, and post-quantum ML-KEM
  • WASM bindings for browser-based encoding/decoding

Integration status: βœ… Python CLI integration is complete as of 2026-02-17. All secret-handling cryptography routes through the Rust meow_crypto_rs PyO3 module. CI enforces RUST_BACKEND_REQUIRED=1. Hardware providers (HSM/YubiKey/TPM) are implemented and CLI-wired (--hsm-slot, --yubikey, --tpm-derive).

Quick Start

# Default features (core crypto only)
cargo add crypto_core

# With hardware security
cargo add crypto_core --features hardware-full

# Pure Rust + WASM (no external dependencies)
cargo add crypto_core --features full-software

# Everything
cargo add crypto_core --features full

Feature Matrix

Feature Description Dependencies
default Core AEAD wrapper only aes-gcm, zeroize
hsm PKCS#11 hardware security modules cryptoki
yubikey YubiKey PIV and FIDO2 yubikey, ctap-hid-fido2
tpm TPM 2.0 platform binding tss-esapi
pure-crypto Pure Rust crypto stack x25519-dalek, argon2, etc.
pq-crypto Post-quantum ML-KEM/ML-DSA (RustCrypto) ml-kem, ml-dsa
liboqs-native Post-quantum via liboqs C library oqs (requires system lib)
wasm Browser WASM bindings wasm-bindgen
hardware-full All hardware features hsm + yubikey + tpm
full-software Pure software crypto pure-crypto + pq-crypto + wasm
full Everything enabled All features

PQ Backend Note: Two post-quantum backends are available:

  • pq-crypto: Pure Rust (RustCrypto ml-kem/ml-dsa 0.1.0-rc) - easy to build, no external deps
  • liboqs-native: C library bindings (liboqs) - production-tested, NIST finalist reference impl

Use pq-crypto for ease; use liboqs-native for production deployments.


Hardware Security

HSM/PKCS#11 (--hsm-provider <uri>)

Connect to any PKCS#11-compliant HSM (SoftHSM, Luna, CloudHSM, etc.):

use crypto_core::{HsmProvider, HsmUri, SecurePin, HsmKeyType};

// Parse PKCS#11 URI (RFC 7512)
let uri = HsmUri::parse("pkcs11:slot-id=0;object=meow-key;token=MyHSM")?;

// Connect with PIN (zeroized on drop)
let provider = HsmProvider::connect_with_uri(&uri, SecurePin::new("1234"))?;

// Generate key in HSM (never leaves hardware)
let key = provider.generate_key("meow-master", HsmKeyType::Aes256)?;

// Encrypt/decrypt using HSM
let ciphertext = provider.encrypt_aes_gcm(&key, &nonce, plaintext, aad)?;
let plaintext = provider.decrypt_aes_gcm(&key, &nonce, &ciphertext, aad)?;

CLI Usage:

meow-encode --hsm-provider "pkcs11:token=MyHSM" -i secret.pdf -o encoded.gif

Security Properties:

  • HSM-001: Keys never leave hardware boundary
  • HSM-002: All operations require authenticated session
  • HSM-003: PINs zeroized immediately after use
  • HSM-004: Session tokens invalidated on drop

YubiKey PIV/FIDO2 (--yubikey-slot <slot>, --fido2)

Use YubiKey for hardware-backed key derivation:

use crypto_core::{YubiKeyProvider, PivSlot, YubiKeyPin, Fido2Provider};

// PIV mode - use existing key
let yk = YubiKeyProvider::connect()?;
yk.authenticate(YubiKeyPin::new("123456"))?;

// Derive key from PIV slot (9a=auth, 9c=signing, 9d=keymgmt, 9e=cardauth)
let derived_key = yk.derive_key_from_slot(PivSlot::Slot9d, &salt)?;

// FIDO2 mode - use hardware attestation
let fido2 = Fido2Provider::discover()?;
let assertion = fido2.get_assertion("meow-encoder.example", &challenge)?;
let derived_key = fido2.derive_key_from_assertion(&assertion, &salt)?;

CLI Usage:

# PIV mode
meow-encode --yubikey-slot 9d -i secret.pdf -o encoded.gif

# FIDO2 mode (touch to authenticate)
meow-encode --fido2 -i secret.pdf -o encoded.gif

Security Properties:

  • YK-001: PIN retry counter (locks after 3 failures)
  • YK-002: Private keys never exportable
  • YK-003: Hardware attestation proves genuine YubiKey
  • YK-004: Touch requirement for high-security operations

TPM 2.0 (--tpm-seal <pcr-mask>)

Seal keys to platform state (boot integrity):

use crypto_core::{TpmProvider, PcrSelection, SealedBlob, TpmAuth};

// Connect to TPM
let tpm = TpmProvider::connect()?;

// Read current PCR values
let pcrs = tpm.read_pcrs(&PcrSelection::from_indices(&[0, 1, 7]))?;

// Seal key to PCR state (only unsealable if PCRs match)
let sealed = tpm.seal(
    &master_key,
    &PcrSelection::from_indices(&[0, 1, 7]),  // Firmware, BIOS, SecureBoot
    &TpmAuth::Password("tpm-auth".into()),
)?;

// Later: unseal (fails if PCRs changed - e.g., BIOS update)
let key = tpm.unseal(&sealed, &TpmAuth::Password("tpm-auth".into()))?;

// Derive key with TPM-mixed entropy
let derived = tpm.derive_key(&sealed, &salt, b"meow-encoder-v1")?;

CLI Usage:

# Seal to boot state (PCRs 0,1,7)
meow-encode --tpm-seal "0,1,7" -i secret.pdf -o encoded.gif

# PCR ranges supported
meow-encode --tpm-seal "0-7" -i secret.pdf -o encoded.gif

Security Properties:

  • TPM-001: Keys bound to specific PCR values
  • TPM-002: Hierarchy separation (endorsement/storage/platform)
  • TPM-003: Auth values never stored in memory longer than needed
  • TPM-004: Boot measurement chain integrity

Pure Rust Crypto

Core Functions

use crypto_core::{
    aes_gcm_encrypt, aes_gcm_decrypt,
    argon2_derive, hkdf_derive, hmac_sha256, sha256,
    SecretKey, Salt, Nonce,
};

// AES-256-GCM encryption
let key = SecretKey::random();
let nonce = Nonce::random();
let ciphertext = aes_gcm_encrypt(&key, &nonce, plaintext, aad)?;
let plaintext = aes_gcm_decrypt(&key, &nonce, &ciphertext, aad)?;

// Argon2id key derivation (memory-hard)
let password = b"correct horse battery staple";
let salt = Salt::random();
let derived = argon2_derive(password, &salt, 512 * 1024, 20, 4)?;  // 512 MiB, 20 iter (production)

// HKDF-SHA256 key expansion
let prk = hkdf_derive(&ikm, &salt, b"meow-encoder-v1", 32)?;

// HMAC-SHA256
let tag = hmac_sha256(&key, &message);

// SHA-256
let hash = sha256(&data);

X25519 Key Exchange

use crypto_core::{X25519KeyPair, x25519_derive_shared};

// Generate ephemeral keypair
let alice = X25519KeyPair::generate();
let bob = X25519KeyPair::generate();

// Exchange public keys and derive shared secret
let alice_shared = x25519_derive_shared(&alice.secret, &bob.public)?;
let bob_shared = x25519_derive_shared(&bob.secret, &alice.public)?;
assert_eq!(alice_shared, bob_shared);  // Same shared secret!

Post-Quantum Crypto (ML-KEM)

use crypto_core::pq::{MlKemKeyPair, mlkem_encapsulate, mlkem_decapsulate, hybrid_key_derive};

// Generate ML-KEM-1024 keypair
let keypair = MlKemKeyPair::generate()?;

// Encapsulate (sender side)
let (ciphertext, shared_secret) = mlkem_encapsulate(&keypair.public)?;

// Decapsulate (receiver side)
let recovered_secret = mlkem_decapsulate(&keypair.secret, &ciphertext)?;

// Hybrid mode: X25519 + ML-KEM (secure if either is secure)
let hybrid_secret = hybrid_key_derive(&x25519_shared, &mlkem_shared, &salt)?;

// Check which backend is active
println!("PQ Backend: {}", crypto_core::pq::backend_name());

Installing liboqs (for liboqs-native feature)

The liboqs-native feature requires the Open Quantum Safe (OQS) C library:

Ubuntu/Debian:

# Add OQS PPA or build from source
sudo apt update
sudo apt install cmake ninja-build libssl-dev
git clone --depth 1 https://github.com/open-quantum-safe/liboqs.git
cd liboqs && mkdir build && cd build
cmake -GNinja -DBUILD_SHARED_LIBS=ON ..
ninja && sudo ninja install
sudo ldconfig

macOS (Homebrew):

brew install liboqs

Fedora/RHEL:

sudo dnf install liboqs-devel

Build with liboqs:

# After installing liboqs system library:
cargo build --features liboqs-native

# Or use pure Rust (no external deps):
cargo build --features pq-crypto

Performance Comparison:

Backend Key Generation Encapsulation Notes
pq-crypto (RustCrypto) ~1.2ms ~0.8ms Pure Rust, easy build
liboqs-native (OQS) ~0.9ms ~0.6ms C lib, ~25% faster

🐱 Cat's Advice: Use pq-crypto for development and CI. Use liboqs-native for production deployments where the ~25% performance gain matters.


WASM Bindings

Build for browser:

wasm-pack build --target web --features wasm

JavaScript Usage

import init, { derive_key, encrypt, decrypt, encode_data, decode_data } from './crypto_core.js';

await init();

// Derive key from password
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = derive_key("my-password", salt, 256 * 1024, 3, 4);

// Encrypt file
const plaintext = new Uint8Array(fileBuffer);
const encrypted = encrypt(key, plaintext, new Uint8Array());

// Full encode (compress + encrypt + format)
const encoded = encode_data(plaintext, "password");

// Decode back
const decoded = decode_data(encoded, "password");

Wire Format (WASM)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Version β”‚   Salt   β”‚  Nonce   β”‚  Orig Len  β”‚  Comp Len  β”‚   Hash     β”‚  Ciphertext β”‚
β”‚  1 byte β”‚ 16 bytes β”‚ 12 bytes β”‚   8 bytes  β”‚   8 bytes  β”‚  32 bytes  β”‚   N bytes   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Safety Properties

AEAD Wrapper (aead_wrapper.rs)

The AEAD wrapper enforces four critical security invariants via type system and tests.

Note: AEAD-001–AEAD-004 are specification stubs in verus_proofs.rs, not yet machine-checked by Verus. The properties below are enforced by Rust's type system and the test suite.

Property Description Enforcement Method
AEAD-001: Nonce Uniqueness Each nonce is used exactly once per key Type-state pattern
AEAD-002: Auth-Then-Output Plaintext only accessible after authentication API design
AEAD-003: Key Zeroization Keys are zeroed when wrapper is dropped Drop trait + Zeroize
AEAD-004: No Counter Wrap Nonce counter panics before overflow Bounds check

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        AeadWrapper                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   NonceManager   β”‚     β”‚          AES-256-GCM             β”‚ β”‚
β”‚  β”‚                  β”‚     β”‚                                  β”‚ β”‚
β”‚  β”‚  counter: u64    │────▢│  encrypt(nonce, pt, aad)        β”‚ β”‚
β”‚  β”‚  random: [u8;4]  β”‚     β”‚  decrypt(nonce, ct, aad)        β”‚ β”‚
β”‚  β”‚  allocated: Set  β”‚     β”‚                                  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚          β”‚                              β”‚                       β”‚
β”‚          β–Ό                              β–Ό                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   UniqueNonce    β”‚     β”‚    AuthenticatedPlaintext       β”‚ β”‚
β”‚  β”‚   (linear type)  β”‚     β”‚    (existential proof)          β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                    key: [u8; 32]                          β”‚  β”‚
β”‚  β”‚                    (ZeroizeOnDrop)                        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Usage

Basic Encryption/Decryption

use crypto_core::{AeadWrapper, KEY_SIZE};

fn main() -> Result<(), crypto_core::AeadError> {
    // Create wrapper with 256-bit key
    let key = [0x42u8; KEY_SIZE];
    let wrapper = AeadWrapper::new(&key)?;

    // Encrypt with automatic nonce management
    let plaintext = b"Secret message";
    let aad = b"additional authenticated data";
    let (nonce, ciphertext) = wrapper.encrypt(plaintext, aad)?;

    // Decrypt and verify authentication
    let authenticated = wrapper.decrypt(&nonce, &ciphertext, aad)?;

    // Access plaintext (proof of authentication)
    println!("Decrypted: {:?}", authenticated.data());

    Ok(())
}

Type-Level Guarantees

// ❌ This won't compile - can't construct AuthenticatedPlaintext directly
let fake = AuthenticatedPlaintext {
    data: vec![1,2,3],
    _authenticated: ()
};

// ❌ This won't compile - UniqueNonce consumed on use
let nonce = nonce_manager.allocate_nonce()?;
let bytes = nonce.take();
let bytes2 = nonce.take();  // Error: nonce moved

// βœ… This is the only way to get authenticated plaintext
let authenticated = wrapper.decrypt(&nonce, &ciphertext, aad)?;
// authenticated.data() is proven to be genuine

Verus Verification

Prerequisites

Install Verus:

git clone https://github.com/verus-lang/verus
cd verus
./tools/get-z3.sh
./tools/build.sh

Pinned versions (recommended):

  • Verus: latest stable from the official repo
  • Z3: version bundled by get-z3.sh

Running Verification

cd crypto_core

# Verify all proofs
verus src/lib.rs

# Verify with verbose output
verus src/lib.rs --output-smt

# Check specific module
verus src/aead_wrapper.rs

Expected Output

verification results:: verified: 8 errors: 0
  nonce_uniqueness_invariant ... verified
  auth_then_output_invariant ... verified
  key_zeroization_invariant ... verified
  no_nonce_reuse ... verified
  NonceManager::allocate_nonce ... verified
  AeadWrapper::encrypt ... verified
  AeadWrapper::decrypt ... verified
  AeadWrapper::new ... verified

Proof Explanations

Nonce Uniqueness (AEAD-001)

Claim: βˆ€ e1, e2 ∈ Encryptions: e1 β‰  e2 ⟹ nonce(e1) β‰  nonce(e2)

Proof:

  1. NonceManager.counter is strictly monotonic (atomic compare-and-swap loop, panics on u64 overflow)
  2. Each allocate_nonce() increments counter exactly once
  3. Nonce = [counter_bytes || random_prefix]
  4. Different counter values ⟹ different nonces ∎
proof fn nonce_uniqueness(nm: &NonceManager, n1: UniqueNonce, n2: UniqueNonce)
    requires old(nm.counter) < nm.counter  // Two allocations happened
    ensures n1.bytes != n2.bytes           // Nonces are different
{
    // counter is monotonic, so counter values differ
    // different counter values => different nonces
}

Auth-Then-Output (AEAD-002)

Claim: AuthenticatedPlaintext ⟹ GCM-Verify(key, nonce, ct, aad) = success

Proof:

  1. AuthenticatedPlaintext has private constructor
  2. Only decrypt() can create it
  3. decrypt() only returns Ok(...) if GCM auth passes
  4. Holding AuthenticatedPlaintext proves auth succeeded ∎
proof fn auth_then_output(ap: AuthenticatedPlaintext)
    ensures exists|k, n, ct, aad|
        decrypt(k, n, ct, aad).is_ok() &&
        decrypt(k, n, ct, aad).unwrap() == ap
{
    // ap can only come from successful decrypt()
    // decrypt() only succeeds if GCM auth passes
}

Key Zeroization (AEAD-003)

Claim: drop(wrapper) ⟹ βˆ€ i ∈ [0, 32): wrapper.key[i] = 0

Proof:

  1. AeadWrapper derives ZeroizeOnDrop
  2. Drop::drop() calls Zeroize::zeroize()
  3. zeroize() overwrites key with zeros
  4. After drop, key memory is zeroed ∎

Security Considerations

What IS Verified

  • βœ… Nonce uniqueness per key
  • βœ… Authentication before plaintext access
  • βœ… Key zeroization on drop
  • βœ… Counter overflow prevention

What is NOT Verified

  • ⚠️ AES-GCM implementation correctness (uses aes-gcm crate)
  • ⚠️ Side-channel resistance (timing, cache, etc.)
  • ⚠️ Random number generator quality (uses getrandom)

Assumptions & Non‑Goals

Assumptions:

  • AES‑GCM is a secure AEAD and the aes-gcm crate is correct
  • OS RNG is secure and unpredictable
  • The Rust compiler and standard library behave correctly

Non‑Goals:

  • Proving AES‑GCM itself
  • Side‑channel resistance (timing/power/EM)
  • Compromised host or malware resistance
  • ⚠️ Memory safety in unsafe blocks (none present)

Assumptions

The proofs assume:

  1. Rust memory safety - Verus inherits Rust's memory model
  2. Correct AES-GCM - We trust the aes-gcm crate
  3. Secure RNG - We trust getrandom provides entropy
  4. No side channels - Constant-time not formally verified

Testing

# Run unit tests
cargo test

# Run with debug assertions
cargo test --features debug_assertions

# Run with Miri for undefined behavior detection
cargo +nightly miri test

Integration with Meow-Encode

This crate is used by the Meow-Encode Rust crypto backend:

// In rust_crypto/src/lib.rs
use crypto_core::AeadWrapper;

pub fn encrypt(key: &[u8], plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, Error> {
    let wrapper = AeadWrapper::new(key)?;
    let (nonce, ciphertext) = wrapper.encrypt(plaintext, aad)?;

    // Return nonce || ciphertext
    let mut result = nonce.to_vec();
    result.extend(ciphertext);
    Ok(result)
}

References

License

CC BY-NC-SA 4.0 - See LICENSE file


CLI Reference

Hardware Key Derivation Flags

Flag Description Example
--hsm-provider <uri> PKCS#11 HSM URI (RFC 7512) pkcs11:token=MyHSM;slot-id=0
--hsm-pin <pin> HSM PIN (or prompt if omitted) --hsm-pin 1234
--yubikey-slot <slot> YubiKey PIV slot 9a, 9c, 9d, 9e
--yubikey-pin <pin> YubiKey PIN (6-8 digits) --yubikey-pin 123456
--fido2 Use FIDO2 hardware attestation (touch required)
--tpm-seal <pcrs> TPM PCR mask for sealing 0,1,7 or 0-7
--tpm-auth <password> TPM authorization value --tpm-auth secret

Examples

# Basic encoding (software crypto)
meow-encode -i secret.pdf -o encoded.gif -p "password"

# HSM-backed encryption
meow-encode --hsm-provider "pkcs11:token=SoftHSM;object=meow-key" \
    -i secret.pdf -o encoded.gif

# YubiKey PIV (Key Management slot)
meow-encode --yubikey-slot 9d --yubikey-pin 123456 \
    -i secret.pdf -o encoded.gif

# FIDO2 hardware attestation
meow-encode --fido2 -i secret.pdf -o encoded.gif

# TPM-sealed to boot state
meow-encode --tpm-seal "0,1,7" --tpm-auth "my-tpm-password" \
    -i secret.pdf -o encoded.gif

# Combined: HSM + Forward Secrecy + Post-Quantum
meow-encode --hsm-provider "pkcs11:token=Luna" --pq \
    --receiver-pubkey alice.pub \
    -i classified.pdf -o quantum-safe.gif

Testing

Unit Tests

# Core tests (no hardware required)
cargo test

# With pure crypto
cargo test --features pure-crypto

# All software features
cargo test --features full-software

Integration Tests (Hardware)

# HSM tests with SoftHSM2
softhsm2-util --init-token --slot 0 --label "TestHSM" --pin 1234 --so-pin 4321
cargo test --features hsm-real -- --ignored

# YubiKey tests (requires connected YubiKey)
cargo test --features yubikey-real -- --ignored

# TPM tests (requires tpm2-abrmd or swtpm)
cargo test --features tpm-real -- --ignored

CI Hardware Simulation

# GitHub Actions example
- name: Test with SoftHSM
  run: |
    sudo apt-get install -y softhsm2
    softhsm2-util --init-token --slot 0 --label CI --pin 1234 --so-pin 4321
    export SOFTHSM2_CONF=/etc/softhsm/softhsm2.conf
    cargo test --features hsm-real

- name: Test with swtpm
  run: |
    sudo apt-get install -y swtpm tpm2-tools
    swtpm socket --tpmstate dir=tpm-state --ctrl type=tcp,port=2322 &
    export TPM2_COMMAND_TCTI=swtpm:host=127.0.0.1,port=2322
    cargo test --features tpm-real

Miri (Undefined Behavior Detection)

cargo +nightly miri test --features pure-crypto

WASM Tests

wasm-pack test --headless --firefox --features wasm

Security Audit Status

Component Status Auditor Date
AEAD Wrapper ⚠️ Type-system enforced (Verus proofs are stubs) Internal 2026-01
Pure Crypto ⏳ Pending Audit - -
HSM Integration ⏳ Pending Audit - -
YubiKey Integration ⏳ Pending Audit - -
TPM Integration ⏳ Pending Audit - -
WASM Bindings ⏳ Pending Audit - -

Disclosure: Submit security issues to security@meow-encoder.example or open a GitHub Security Advisory.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              crypto_core                                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚   Hardware Layer    β”‚     β”‚   Pure Crypto       β”‚     β”‚   WASM Bindings    β”‚β”‚
β”‚  β”‚                     β”‚     β”‚                     β”‚     β”‚                    β”‚β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚β”‚
β”‚  β”‚  β”‚  hsm.rs       β”‚  β”‚     β”‚  β”‚ AES-256-GCM   β”‚  β”‚     β”‚  β”‚ wasm.rs      β”‚  β”‚β”‚
β”‚  β”‚  β”‚  (PKCS#11)    β”‚  β”‚     β”‚  β”‚ X25519        β”‚  β”‚     β”‚  β”‚              β”‚  β”‚β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β”‚  β”‚ Argon2id      β”‚  β”‚     β”‚  β”‚ encrypt()    β”‚  β”‚β”‚
β”‚  β”‚                     β”‚     β”‚  β”‚ HKDF-SHA256   β”‚  β”‚     β”‚  β”‚ decrypt()    β”‚  β”‚β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     β”‚  β”‚ HMAC-SHA256   β”‚  β”‚     β”‚  β”‚ derive_key() β”‚  β”‚β”‚
β”‚  β”‚  β”‚ yubikey_piv.rsβ”‚  β”‚     β”‚  β”‚ SHA-256       β”‚  β”‚     β”‚  β”‚ encode_data()β”‚  β”‚β”‚
β”‚  β”‚  β”‚ (PIV/FIDO2)   β”‚  β”‚     β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β”‚  β”‚ decode_data()β”‚  β”‚β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β”‚                     β”‚     β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚β”‚
β”‚  β”‚                     β”‚     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     β”‚                    β”‚β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     β”‚  β”‚ Post-Quantum  β”‚  β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β”‚  β”‚  β”‚  tpm.rs       β”‚  β”‚     β”‚  β”‚ ML-KEM-1024   β”‚  β”‚                           β”‚
β”‚  β”‚  β”‚  (TPM 2.0)    β”‚  β”‚     β”‚  β”‚ ML-DSA-65     β”‚  β”‚                           β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                           β”‚
β”‚  β”‚                     β”‚     β”‚                     β”‚                           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚                                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚                    Core (type-system enforced, Verus stubs)                 β”‚β”‚
β”‚  β”‚                                                                             β”‚β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚β”‚
β”‚  β”‚  β”‚                          AeadWrapper                                    β”‚β”‚β”‚
β”‚  β”‚  β”‚  - AEAD-001: Nonce Uniqueness (type-state + ghost tracking)            β”‚β”‚β”‚
β”‚  β”‚  β”‚  - AEAD-002: Auth-Then-Output (existential type proof)                 β”‚β”‚β”‚
β”‚  β”‚  β”‚  - AEAD-003: Key Zeroization (ZeroizeOnDrop)                           β”‚β”‚β”‚
β”‚  β”‚  β”‚  - AEAD-004: No Counter Wrap (bounds check)                            β”‚β”‚β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β”‚                                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Changelog

See CHANGELOG.md for version history.

Contributing

See CONTRIBUTING.md for development guidelines.

License

CC BY-NC-SA 4.0 - See LICENSE file