TypeDialog/docs/encryption/encryption-unified-architecture.md
2025-12-24 03:11:32 +00:00

11 KiB

Unified Encryption Architecture

This document explains the updated encryption architecture for typedialog and how it integrates with the unified encryption system from prov-ecosystem.

Overview

typedialog now uses a unified encryption API from the encrypt crate, eliminating backend-specific code and enabling support for multiple encryption backends (Age, SOPS, SecretumVault, AWS/GCP/Azure KMS) through a single API.

Key Benefits:

  • Single code path supports all backends
  • Configuration-driven backend selection
  • Multi-backend support in TOML and Nickel schemas
  • Post-quantum cryptography ready (via SecretumVault)
  • Cleaner, more maintainable code

Architecture Changes

Before: Direct Backend Instantiation

// OLD: Direct Age backend instantiation
use encrypt::backend::age::AgeBackend;

let backend = AgeBackend::with_defaults()?;
let ciphertext = backend.encrypt(&plaintext)?;

// To support other backends, need separate code paths
#[match]
"sops" => { /* SOPS code */ }
"rustyvault" => { /* RustyVault code */ }

After: Unified API with BackendSpec

// NEW: Configuration-driven, backend-agnostic
use encrypt::{encrypt, BackendSpec};

// Same code for all backends
let spec = BackendSpec::age_default();
let ciphertext = encrypt(&plaintext, &spec)?;

// Or SOPS
let spec = BackendSpec::sops();
let ciphertext = encrypt(&plaintext, &spec)?;

// Or KMS
let spec = BackendSpec::aws_kms(region, key_id);
let ciphertext = encrypt(&plaintext, &spec)?;

Integration Points

1. TOML Form Configuration

No changes required for existing TOML configurations. The system transparently uses the new API:

[[fields]]
name = "password"
type = "password"
sensitive = true
encryption_backend = "age"
encryption_config = { key_file = "~/.age/key.txt" }

The encryption_bridge module automatically converts this to BackendSpec::age(...) and uses the unified API.

2. Nickel Schema Integration

Nickel support remains unchanged but now uses the unified backend system:

{
  # Age backend for development
  dev_password | Sensitive Backend="age" Key="~/.age/key.txt" = "",

  # SOPS for staging
  staging_secret | Sensitive Backend="sops" = "",

  # SecretumVault Transit Engine for production (post-quantum)
  prod_token | Sensitive Backend="secretumvault"
             Vault="https://vault.internal:8200"
             Key="app-key" = "",

  # AWS KMS for cloud-native deployments
  aws_secret | Sensitive Backend="awskms"
             Region="us-east-1"
             KeyId="arn:aws:kms:..." = "",
}

3. Internal Encryption Function

The transform_sensitive_value() function now uses the unified API:

// File: crates/typedialog-core/src/helpers.rs

fn transform_sensitive_value(
    value: &Value,
    field: &FieldDefinition,
    context: &EncryptionContext,
    _global_config: Option<&EncryptionDefaults>,
) -> Result<Value> {
    // Convert field definition to BackendSpec
    let spec = crate::encryption_bridge::field_to_backend_spec(field, None)?;

    // Use unified API
    let plaintext = serde_json::to_string(value)?;
    let ciphertext = encrypt::encrypt(&plaintext, &spec)?;

    Ok(Value::String(ciphertext))
}

New Bridge Module

New encryption_bridge.rs module provides seamless conversion:

// File: crates/typedialog-core/src/encryption_bridge.rs

pub fn field_to_backend_spec(
    field: &FieldDefinition,
    default_backend: Option<&str>,
) -> Result<encrypt::BackendSpec>

Conversion Logic:

  1. Reads field.encryption_backend (or uses default)
  2. Extracts field.encryption_config (backend-specific settings)
  3. Validates required configuration for the backend
  4. Returns BackendSpec ready for use with encrypt::encrypt()

Supported Backends:

  • ✓ Age (with custom key paths)
  • ✓ SOPS (minimal config)
  • ✓ SecretumVault (vault_addr, vault_token, key_name)
  • ✓ AWS KMS (region, key_id)
  • ✓ GCP KMS (project_id, key_ring, crypto_key, location)
  • ✓ Azure KMS (vault_name, tenant_id)

Configuration Changes

Cargo.toml

No changes required - encryption support includes all commonly used backends by default.

To customize backends:

[dependencies]
# Default: age + other major backends
typedialog-core = { path = "...", features = ["encryption"] }

# Only Age
typedialog-core = { path = "...", features = ["encryption"] }
# (Age is default in encrypt crate)

# Custom selection
typedialog-core = { path = "...", features = ["encryption"] }
# Depends on encrypt crate configuration

Environment Variables

Backend-specific configuration via environment:

Age:

# Uses ~/.age/key.txt by default
# Or specify via field config: encryption_config = { key_file = "/custom/path" }

SOPS:

# Uses .sops.yaml in current/parent directories

SecretumVault:

export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="hvs.CAAA..."

AWS KMS:

export AWS_REGION="us-east-1"
# AWS credentials from standard chain (env vars, ~/.aws/credentials, IAM roles)

GCP KMS:

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"

Azure KMS:

# Azure CLI authentication or environment variables

Error Handling

Enhanced error messages guide users through troubleshooting:

Error: Encryption failed: Backend 'age' not available.
  Enable feature 'age' in Cargo.toml

Error: Encryption failed: SecretumVault backend requires
  vault_addr in encryption_config

Error: Encryption failed: AWS KMS backend requires region
  in encryption_config

Testing

Running Encryption Tests

# All encryption tests
cargo test --features encryption

# Only encryption integration tests
cargo test --features encryption --test encryption_integration

# Specific test
cargo test --features encryption test_age_roundtrip_encrypt_decrypt

Test Results

All 15 encryption integration tests pass:

  • ✓ 7 encryption behavior tests
  • ✓ 8 Age backend roundtrip tests
running 15 tests
test encryption_tests::test_explicit_non_sensitive_overrides_password_type ... ok
test encryption_tests::test_auto_detect_password_field_as_sensitive ... ok
test encryption_tests::test_redaction_preserves_non_sensitive ... ok
test encryption_tests::test_multiple_sensitive_fields ... ok
test encryption_tests::test_redaction_in_json_output ... ok
test encryption_tests::test_unknown_backend_error ... ok
test encryption_tests::test_redaction_in_yaml_output ... ok
test age_roundtrip_tests::test_age_backend_availability ... ok
test age_roundtrip_tests::test_age_invalid_ciphertext_fails ... ok
test age_roundtrip_tests::test_age_encryption_produces_ciphertext ... ok
test age_roundtrip_tests::test_age_roundtrip_encrypt_decrypt ... ok
test age_roundtrip_tests::test_age_handles_empty_string ... ok
test age_roundtrip_tests::test_age_handles_unicode ... ok
test age_roundtrip_tests::test_age_encryption_different_ciphertexts ... ok
test age_roundtrip_tests::test_age_handles_large_values ... ok

test result: ok. 15 passed; 0 failed

Migration Path

For Existing Users

No action required. The system is backward compatible:

  1. Existing TOML forms work unchanged
  2. Existing Nickel schemas work unchanged
  3. Internal implementation now uses unified API
  4. No visible changes to users

For New Deployments

Can now use additional backends:

# Now supports more backends in single codebase
[[fields]]
name = "secret"
type = "text"
sensitive = true
encryption_backend = "secretumvault"  # Or awskms, gcpkms, azurekms
encryption_config = { vault_addr = "...", vault_token = "..." }

For Code Extending typedialog

If extending typedialog with custom encryption logic:

// OLD: Manual backend instantiation (still works)
use encrypt::backend::age::AgeBackend;
let backend = AgeBackend::new(pub_key, priv_key)?;

// NEW: Use bridge module + unified API (recommended)
use encrypt::encrypt;
use typedialog_core::encryption_bridge;

let spec = encryption_bridge::field_to_backend_spec(&field, None)?;
let ciphertext = encrypt(&plaintext, &spec)?;

Multi-Backend Support

Same Code, Different Configs

# Development (Age)
[[fields]]
name = "db_password"
sensitive = true
encryption_backend = "age"

# Production (SecretumVault - post-quantum)
[[fields]]
name = "db_password"
sensitive = true
encryption_backend = "secretumvault"
encryption_config = { vault_addr = "https://vault.prod:8200", vault_token = "..." }

Same Rust code handles both without changes.

CLI Overrides

# Override backend from command line
typedialog form config.toml --encrypt --backend secretumvault

# Use with environment variables
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="hvs.CAAA..."
typedialog form config.toml --encrypt --backend secretumvault

Feature Flags

Backends are feature-gated in the encrypt crate:

// With feature enabled
#[cfg(feature = "age")]
{
    let spec = BackendSpec::age_default();
    encrypt(&plaintext, &spec)?;  // Works
}

// Without feature
#[cfg(not(feature = "age"))]
{
    let spec = BackendSpec::age_default();
    encrypt(&plaintext, &spec)?;  // Returns: Backend 'age' not available
}

Post-Quantum Cryptography

SecretumVault Transit Engine

For post-quantum cryptography support, use SecretumVault with ML-KEM/ML-DSA:

[[fields]]
name = "pqc_secret"
sensitive = true
encryption_backend = "secretumvault"
encryption_config = {
    vault_addr = "https://pq-vault.internal:8200",
    vault_token = "hvs.CAAA...",
    key_name = "pqc-key"  # Uses ML-KEM encapsulation, ML-DSA signatures
}

Requirements:

  • SecretumVault server configured with Transit Engine
  • Post-quantum crypto backend enabled (aws-lc-rs or Tongsuo)

Troubleshooting

"Backend not available" Error

Problem: Encryption fails with "Backend 'age' not available"

Solution: Feature may not be enabled in encrypt crate. Check:

# Check available backends
cargo build --features encryption --verbose

# Look for feature compilation
# It should show: "Compiling encrypt ... with features: age,..."

"Invalid ciphertext" After Update

Problem: Old ciphertexts fail to decrypt

Solution: Age format hasn't changed. Verify:

  1. Same Age key is used
  2. Ciphertext format is valid (hex-encoded)
  3. Key file permissions: chmod 600 ~/.age/key.txt

Form TOML Backward Compatibility

Problem: Existing TOML forms stop working after update

Solution: No breaking changes. Forms should work as-is. If not:

  1. Verify encryption_backend name is valid
  2. Check encryption_config required fields
  3. Test with: cargo test --features encryption

Testing with Mock Backend

For faster tests without real encryption keys:

# In development/testing
cargo test --features test-util

MockBackend provides deterministic encryption for CI/CD:

#[cfg(test)]
use encrypt::test_util::MockBackend;

let backend = MockBackend::new();
let ct = backend.encrypt("secret")?;
// Fast, reproducible, no real keys needed

See Also