TypeDialog/docs/encryption/encryption-unified-architecture.md
2026-01-11 22:35:49 +00:00

12 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 */ }
```text

### After: Unified API with BackendSpec

```rust
// 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)?;
```text

## Integration Points

### 1. TOML Form Configuration

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

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

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:

```nickel
{
  # 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:..." = "",
}
```text

### 3. Internal Encryption Function

The `transform_sensitive_value()` function now uses the unified API:

```rust
// 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))
}
```text

## New Bridge Module

New `encryption_bridge.rs` module provides seamless conversion:

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

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

**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:

```toml
[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
```text

### Environment Variables

Backend-specific configuration via environment:

**Age:**

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

**SOPS:**

```bash
# Uses .sops.yaml in current/parent directories
```text

**SecretumVault:**

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

**AWS KMS:**

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

**GCP KMS:**

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

**Azure KMS:**

```bash
# Azure CLI authentication or environment variables
```text

## Error Handling

Enhanced error messages guide users through troubleshooting:

```text
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
```text

## Testing

### Running Encryption Tests

```bash
# 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
```text

### Test Results

All 15 encryption integration tests pass:

-  7 encryption behavior tests
-  8 Age backend roundtrip tests

```text
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
```text

## 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:

```toml
# 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 = "..." }
```text

### For Code Extending typedialog

If extending typedialog with custom encryption logic:

```rust
// 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)?;
```text

## Multi-Backend Support

### Same Code, Different Configs

```toml
# 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 = "..." }
```text

Same Rust code handles both without changes.

### CLI Overrides

```bash
# 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
```text

## Feature Flags

Backends are feature-gated in the encrypt crate:

```rust
// 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
}
```text

## Post-Quantum Cryptography

### SecretumVault Transit Engine

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

```toml
[[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
}
```text

**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:

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

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

### "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:

```bash
# In development/testing
cargo test --features test-util
```text

MockBackend provides deterministic encryption for CI/CD:

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

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

## See Also

- [encryption-quick-start.md](encryption-quick-start.md) - Getting started with encryption
- [encryption-services-setup.md](encryption-services-setup.md) - Setting up encryption services
- [../../prov-ecosystem/docs/guides/ENCRYPTION.md](../../prov-ecosystem/docs/guides/ENCRYPTION.md) - Comprehensive encryption guide
- [encryption_bridge.rs](crates/typedialog-core/src/encryption_bridge.rs) - Bridge module source
- [../../prov-ecosystem/crates/encrypt](../../prov-ecosystem/crates/encrypt) - encrypt crate source