use std::sync::{Condvar, Mutex};
use shadow_crypt_core::{
errors::KeyDerivationError, memory::SecureKey, report::KeyDerivationReport,
};
use crate::errors::{WorkflowError, WorkflowResult};
pub const MAX_KDF_MEMORY_KIB: u32 = 8 * 1024 * 1024; pub const MAX_KDF_ITERATIONS: u32 = 1_000;
pub const MAX_KDF_PARALLELISM: u32 = 256;
pub const MAX_KDF_KEY_SIZE: u8 = 64;
pub fn validate_untrusted_kdf_params(
memory_cost: u32,
time_cost: u32,
parallelism: u32,
key_size: u8,
) -> WorkflowResult<()> {
if memory_cost > MAX_KDF_MEMORY_KIB {
return Err(WorkflowError::UserInput(format!(
"KDF memory cost in file header is too large: {} KiB (max {} KiB)",
memory_cost, MAX_KDF_MEMORY_KIB
)));
}
if time_cost > MAX_KDF_ITERATIONS {
return Err(WorkflowError::UserInput(format!(
"KDF iteration count in file header is too large: {} (max {})",
time_cost, MAX_KDF_ITERATIONS
)));
}
if parallelism > MAX_KDF_PARALLELISM {
return Err(WorkflowError::UserInput(format!(
"KDF parallelism in file header is too large: {} (max {})",
parallelism, MAX_KDF_PARALLELISM
)));
}
if key_size > MAX_KDF_KEY_SIZE {
return Err(WorkflowError::UserInput(format!(
"KDF key size in file header is too large: {} bytes (max {})",
key_size, MAX_KDF_KEY_SIZE
)));
}
Ok(())
}
pub fn derive_key_from_untrusted_params(
memory_cost: u32,
time_cost: u32,
parallelism: u32,
key_size: u8,
derive: impl FnOnce() -> Result<(SecureKey, KeyDerivationReport), KeyDerivationError>,
) -> WorkflowResult<SecureKey> {
validate_untrusted_kdf_params(memory_cost, time_cost, parallelism, key_size)?;
let (key, _) = with_kdf_memory_permit(memory_cost, derive)?;
Ok(key)
}
pub const KDF_MEMORY_BUDGET_KIB: u64 = 4 * 1024 * 1024;
static KDF_GATE: KdfGate = KdfGate::new(KDF_MEMORY_BUDGET_KIB);
pub fn with_kdf_memory_permit<T>(cost_kib: u32, f: impl FnOnce() -> T) -> T {
let _permit = KDF_GATE.acquire(cost_kib as u64);
f()
}
struct KdfGate {
budget_kib: u64,
in_use_kib: Mutex<u64>,
released: Condvar,
}
impl KdfGate {
const fn new(budget_kib: u64) -> Self {
Self {
budget_kib,
in_use_kib: Mutex::new(0),
released: Condvar::new(),
}
}
fn acquire(&self, cost_kib: u64) -> KdfPermit<'_> {
let cost_kib = cost_kib.clamp(1, self.budget_kib);
let mut in_use = self.in_use_kib.lock().unwrap_or_else(|e| e.into_inner());
while *in_use + cost_kib > self.budget_kib {
in_use = self
.released
.wait(in_use)
.unwrap_or_else(|e| e.into_inner());
}
*in_use += cost_kib;
KdfPermit {
gate: self,
cost_kib,
}
}
}
struct KdfPermit<'a> {
gate: &'a KdfGate,
cost_kib: u64,
}
impl Drop for KdfPermit<'_> {
fn drop(&mut self) {
let mut in_use = self
.gate
.in_use_kib
.lock()
.unwrap_or_else(|e| e.into_inner());
*in_use -= self.cost_kib;
drop(in_use);
self.gate.released.notify_all();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_kdf_params_accepted() {
assert!(validate_untrusted_kdf_params(1024, 1, 1, 32).is_ok());
}
#[test]
fn test_memory_cost_too_large() {
assert!(validate_untrusted_kdf_params(MAX_KDF_MEMORY_KIB + 1, 1, 1, 32).is_err());
}
#[test]
fn test_memory_cost_at_limit_accepted() {
assert!(validate_untrusted_kdf_params(MAX_KDF_MEMORY_KIB, 1, 1, 32).is_ok());
}
#[test]
fn test_iterations_too_large() {
assert!(validate_untrusted_kdf_params(1024, MAX_KDF_ITERATIONS + 1, 1, 32).is_err());
}
#[test]
fn test_parallelism_too_large() {
assert!(validate_untrusted_kdf_params(1024, 1, MAX_KDF_PARALLELISM + 1, 32).is_err());
}
#[test]
fn test_key_size_too_large() {
assert!(validate_untrusted_kdf_params(1024, 1, 1, MAX_KDF_KEY_SIZE + 1).is_err());
}
#[test]
fn test_gate_allows_cost_within_budget() {
let gate = KdfGate::new(100);
let permit = gate.acquire(60);
assert_eq!(*gate.in_use_kib.lock().unwrap(), 60);
drop(permit);
assert_eq!(*gate.in_use_kib.lock().unwrap(), 0);
}
#[test]
fn test_gate_clamps_oversized_cost_to_budget() {
let gate = KdfGate::new(100);
let permit = gate.acquire(1_000_000);
assert_eq!(*gate.in_use_kib.lock().unwrap(), 100);
drop(permit);
}
#[test]
fn test_gate_zero_cost_still_reserves() {
let gate = KdfGate::new(100);
let permit = gate.acquire(0);
assert_eq!(*gate.in_use_kib.lock().unwrap(), 1);
drop(permit);
}
#[test]
fn test_gate_limits_concurrent_memory_use() {
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
let gate = Arc::new(KdfGate::new(100));
let active = Arc::new(AtomicU64::new(0));
let max_active = Arc::new(AtomicU64::new(0));
let handles: Vec<_> = (0..8)
.map(|_| {
let gate = Arc::clone(&gate);
let active = Arc::clone(&active);
let max_active = Arc::clone(&max_active);
std::thread::spawn(move || {
let _permit = gate.acquire(50);
let now = active.fetch_add(1, Ordering::SeqCst) + 1;
max_active.fetch_max(now, Ordering::SeqCst);
std::thread::sleep(std::time::Duration::from_millis(10));
active.fetch_sub(1, Ordering::SeqCst);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert!(max_active.load(Ordering::SeqCst) <= 2);
assert_eq!(*gate.in_use_kib.lock().unwrap(), 0);
}
#[test]
fn test_with_kdf_memory_permit_returns_value() {
let result = with_kdf_memory_permit(1024, || 42);
assert_eq!(result, 42);
}
#[test]
fn test_production_params_accepted() {
let params = shadow_crypt_core::v1::key::KeyDerivationParams::production_defaults();
assert!(
validate_untrusted_kdf_params(
params.memory_cost,
params.time_cost,
params.parallelism,
params.key_size
)
.is_ok()
);
}
}