shadow_crypt_shell/
kdf.rs1use std::sync::{Condvar, Mutex};
9
10use shadow_crypt_core::{
11 errors::KeyDerivationError, memory::SecureKey, report::KeyDerivationReport,
12};
13
14use crate::errors::{WorkflowError, WorkflowResult};
15
16pub const MAX_KDF_MEMORY_KIB: u32 = 8 * 1024 * 1024; pub const MAX_KDF_ITERATIONS: u32 = 1_000;
20pub const MAX_KDF_PARALLELISM: u32 = 256;
21pub const MAX_KDF_KEY_SIZE: u8 = 64;
22
23pub fn validate_untrusted_kdf_params(
28 memory_cost: u32,
29 time_cost: u32,
30 parallelism: u32,
31 key_size: u8,
32) -> WorkflowResult<()> {
33 if memory_cost > MAX_KDF_MEMORY_KIB {
34 return Err(WorkflowError::UserInput(format!(
35 "KDF memory cost in file header is too large: {} KiB (max {} KiB)",
36 memory_cost, MAX_KDF_MEMORY_KIB
37 )));
38 }
39 if time_cost > MAX_KDF_ITERATIONS {
40 return Err(WorkflowError::UserInput(format!(
41 "KDF iteration count in file header is too large: {} (max {})",
42 time_cost, MAX_KDF_ITERATIONS
43 )));
44 }
45 if parallelism > MAX_KDF_PARALLELISM {
46 return Err(WorkflowError::UserInput(format!(
47 "KDF parallelism in file header is too large: {} (max {})",
48 parallelism, MAX_KDF_PARALLELISM
49 )));
50 }
51 if key_size > MAX_KDF_KEY_SIZE {
52 return Err(WorkflowError::UserInput(format!(
53 "KDF key size in file header is too large: {} bytes (max {})",
54 key_size, MAX_KDF_KEY_SIZE
55 )));
56 }
57 Ok(())
58}
59
60pub fn derive_key_from_untrusted_params(
71 memory_cost: u32,
72 time_cost: u32,
73 parallelism: u32,
74 key_size: u8,
75 derive: impl FnOnce() -> Result<(SecureKey, KeyDerivationReport), KeyDerivationError>,
76) -> WorkflowResult<SecureKey> {
77 validate_untrusted_kdf_params(memory_cost, time_cost, parallelism, key_size)?;
78 let (key, _) = with_kdf_memory_permit(memory_cost, derive)?;
79 Ok(key)
80}
81
82pub const KDF_MEMORY_BUDGET_KIB: u64 = 4 * 1024 * 1024; static KDF_GATE: KdfGate = KdfGate::new(KDF_MEMORY_BUDGET_KIB);
94
95pub fn with_kdf_memory_permit<T>(cost_kib: u32, f: impl FnOnce() -> T) -> T {
102 let _permit = KDF_GATE.acquire(cost_kib as u64);
103 f()
104}
105
106struct KdfGate {
107 budget_kib: u64,
108 in_use_kib: Mutex<u64>,
109 released: Condvar,
110}
111
112impl KdfGate {
113 const fn new(budget_kib: u64) -> Self {
114 Self {
115 budget_kib,
116 in_use_kib: Mutex::new(0),
117 released: Condvar::new(),
118 }
119 }
120
121 fn acquire(&self, cost_kib: u64) -> KdfPermit<'_> {
122 let cost_kib = cost_kib.clamp(1, self.budget_kib);
125 let mut in_use = self.in_use_kib.lock().unwrap_or_else(|e| e.into_inner());
126 while *in_use + cost_kib > self.budget_kib {
127 in_use = self
128 .released
129 .wait(in_use)
130 .unwrap_or_else(|e| e.into_inner());
131 }
132 *in_use += cost_kib;
133 KdfPermit {
134 gate: self,
135 cost_kib,
136 }
137 }
138}
139
140struct KdfPermit<'a> {
141 gate: &'a KdfGate,
142 cost_kib: u64,
143}
144
145impl Drop for KdfPermit<'_> {
146 fn drop(&mut self) {
147 let mut in_use = self
148 .gate
149 .in_use_kib
150 .lock()
151 .unwrap_or_else(|e| e.into_inner());
152 *in_use -= self.cost_kib;
153 drop(in_use);
154 self.gate.released.notify_all();
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_valid_kdf_params_accepted() {
164 assert!(validate_untrusted_kdf_params(1024, 1, 1, 32).is_ok());
165 }
166
167 #[test]
168 fn test_memory_cost_too_large() {
169 assert!(validate_untrusted_kdf_params(MAX_KDF_MEMORY_KIB + 1, 1, 1, 32).is_err());
170 }
171
172 #[test]
173 fn test_memory_cost_at_limit_accepted() {
174 assert!(validate_untrusted_kdf_params(MAX_KDF_MEMORY_KIB, 1, 1, 32).is_ok());
175 }
176
177 #[test]
178 fn test_iterations_too_large() {
179 assert!(validate_untrusted_kdf_params(1024, MAX_KDF_ITERATIONS + 1, 1, 32).is_err());
180 }
181
182 #[test]
183 fn test_parallelism_too_large() {
184 assert!(validate_untrusted_kdf_params(1024, 1, MAX_KDF_PARALLELISM + 1, 32).is_err());
185 }
186
187 #[test]
188 fn test_key_size_too_large() {
189 assert!(validate_untrusted_kdf_params(1024, 1, 1, MAX_KDF_KEY_SIZE + 1).is_err());
190 }
191
192 #[test]
193 fn test_gate_allows_cost_within_budget() {
194 let gate = KdfGate::new(100);
195 let permit = gate.acquire(60);
196 assert_eq!(*gate.in_use_kib.lock().unwrap(), 60);
197 drop(permit);
198 assert_eq!(*gate.in_use_kib.lock().unwrap(), 0);
199 }
200
201 #[test]
202 fn test_gate_clamps_oversized_cost_to_budget() {
203 let gate = KdfGate::new(100);
204 let permit = gate.acquire(1_000_000);
206 assert_eq!(*gate.in_use_kib.lock().unwrap(), 100);
207 drop(permit);
208 }
209
210 #[test]
211 fn test_gate_zero_cost_still_reserves() {
212 let gate = KdfGate::new(100);
213 let permit = gate.acquire(0);
214 assert_eq!(*gate.in_use_kib.lock().unwrap(), 1);
215 drop(permit);
216 }
217
218 #[test]
219 fn test_gate_limits_concurrent_memory_use() {
220 use std::sync::{
221 Arc,
222 atomic::{AtomicU64, Ordering},
223 };
224
225 let gate = Arc::new(KdfGate::new(100));
227 let active = Arc::new(AtomicU64::new(0));
228 let max_active = Arc::new(AtomicU64::new(0));
229
230 let handles: Vec<_> = (0..8)
231 .map(|_| {
232 let gate = Arc::clone(&gate);
233 let active = Arc::clone(&active);
234 let max_active = Arc::clone(&max_active);
235 std::thread::spawn(move || {
236 let _permit = gate.acquire(50);
237 let now = active.fetch_add(1, Ordering::SeqCst) + 1;
238 max_active.fetch_max(now, Ordering::SeqCst);
239 std::thread::sleep(std::time::Duration::from_millis(10));
240 active.fetch_sub(1, Ordering::SeqCst);
241 })
242 })
243 .collect();
244
245 for handle in handles {
246 handle.join().unwrap();
247 }
248
249 assert!(max_active.load(Ordering::SeqCst) <= 2);
250 assert_eq!(*gate.in_use_kib.lock().unwrap(), 0);
251 }
252
253 #[test]
254 fn test_with_kdf_memory_permit_returns_value() {
255 let result = with_kdf_memory_permit(1024, || 42);
256 assert_eq!(result, 42);
257 }
258
259 #[test]
260 fn test_production_params_accepted() {
261 let params = shadow_crypt_core::v1::key::KeyDerivationParams::production_defaults();
262 assert!(
263 validate_untrusted_kdf_params(
264 params.memory_cost,
265 params.time_cost,
266 params.parallelism,
267 params.key_size
268 )
269 .is_ok()
270 );
271 }
272}