Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ gcc -o example example.c -L target/release -lcachekit_core -I include
│ Each tenant key provides: │
│ • Cryptographic isolation (compromise one ≠ compromise all) │
│ • Domain separation (cache vs auth vs sessions) │
│ • Forward secrecy with key rotation
│ • Master-key rotation via decrypt-only keyring (grace window)
│ │
└─────────────────────────────────────────────────────────────────┘
```
Expand Down Expand Up @@ -263,7 +263,7 @@ cachekit-core/
│ │ ├── mod.rs # Module exports
│ │ ├── core.rs # AES-256-GCM implementation
│ │ ├── key_derivation.rs # HKDF-SHA256 + tenant isolation
│ │ └── key_rotation.rs # Graceful key rotation support
│ │ └── keyring.rs # Multi-key decrypt keyring (master-key rotation)
│ │
│ └── ffi/ # (feature = "ffi")
│ ├── mod.rs # FFI exports
Expand Down
37 changes: 19 additions & 18 deletions src/encryption/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,25 @@ pub enum EncryptionError {
#[error("Nonce counter exhausted - key rotation required")]
NonceCounterExhausted,

#[error("Key rotation not yet implemented")]
NotImplemented(String),
#[error("Invalid master key length: expected at least 16 bytes, got {0}")]
InvalidMasterKeyLength(usize),

#[error(
"Keyring cap exceeded: at most {max} decrypt-only keys allowed, got {0}",
max = super::keyring::MAX_DECRYPT_ONLY_KEYS
)]
KeyringCapExceeded(usize),

#[error(
"Current key must not appear in the decrypt-only list (forward-only rotation invariant)"
)]
CurrentKeyInDecryptOnlyList,

#[error("Keyring entry index {index} out of range (entry count {count})")]
KeyringIndexOutOfRange { index: usize, count: usize },

#[error("Key derivation failed: {0}")]
KeyDerivation(#[from] super::key_derivation::KeyDerivationError),
}

/// Zero-knowledge encryptor using AES-256-GCM with hardware acceleration detection
Expand Down Expand Up @@ -480,22 +497,6 @@ impl ZeroKnowledgeEncryptor {
.unwrap_or_else(|_| OperationMetrics::new())
}

/// Key rotation API (stub for future implementation)
///
/// This method will support gradual key migration to allow rotating encryption keys
/// without downtime. Future implementation will:
/// - Support dual-key mode (read from both old and new key, write with new key only)
/// - Add version byte to ciphertext header indicating which key was used
/// - Implement gradual migration strategy
///
/// Currently returns NotImplemented error.
pub fn rotate_key(&mut self, _new_master_key: &[u8]) -> Result<(), EncryptionError> {
Err(EncryptionError::NotImplemented(
"Key rotation will be implemented in a future release with gradual migration support"
.into(),
))
}

/// Encrypt data using AES-256-GCM with authenticated additional data (wasm32)
///
/// Uses RustCrypto's `aes-gcm` crate (pure Rust, compiles on wasm32-unknown-unknown).
Expand Down
14 changes: 13 additions & 1 deletion src/encryption/key_derivation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub fn derive_tenant_keys(
// Allow unused_assignments: Zeroize derive macro generates assignment code for #[zeroize(skip)]
// fields that triggers false positive in Rust 1.92+. The tenant_id field IS read in tests/fuzz.
#[allow(unused_assignments)]
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct TenantKeys {
pub encryption_key: [u8; 32],
pub authentication_key: [u8; 32],
Expand All @@ -170,6 +170,18 @@ pub struct TenantKeys {
pub tenant_id: String,
}

// Manual Debug: key material must never reach logs via `{:?}` (CWE-215).
// Only the tenant id and the encryption-key fingerprint are printed.
impl std::fmt::Debug for TenantKeys {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TenantKeys")
.field("tenant_id", &self.tenant_id)
.field("encryption_fingerprint", &self.encryption_fingerprint())
.field("keys", &"<redacted>")
.finish()
}
}

impl TenantKeys {
/// Get fingerprint for the encryption key
pub fn encryption_fingerprint(&self) -> [u8; 16] {
Expand Down
Loading
Loading