1use crate::errors::{SecurityError, SecurityResult};
4use constant_time_eq::constant_time_eq;
5use secrecy::{ExposeSecret, Secret};
6use zeroize::Zeroizing;
7
8const MIN_KEY_SIZE: usize = 32; const MAX_KEY_AGE_DAYS: i64 = 90;
13
14pub struct CryptoValidator {
16 strict_mode: bool,
17}
18
19impl CryptoValidator {
20 pub fn new(strict_mode: bool) -> Self {
22 Self { strict_mode }
23 }
24
25 pub fn strict() -> Self {
27 Self::new(true)
28 }
29
30 pub fn validate_key(&self, key: &[u8]) -> SecurityResult<()> {
32 if key.len() < MIN_KEY_SIZE {
34 return Err(SecurityError::CryptoError(format!(
35 "Key size too small: {} bytes (minimum: {})",
36 key.len(),
37 MIN_KEY_SIZE
38 )));
39 }
40
41 if self.is_weak_key(key) {
43 return Err(SecurityError::CryptoError(
44 "Weak key detected".to_string(),
45 ));
46 }
47
48 if self.strict_mode && !self.has_sufficient_entropy(key) {
50 return Err(SecurityError::CryptoError(
51 "Key has insufficient entropy".to_string(),
52 ));
53 }
54
55 Ok(())
56 }
57
58 fn is_weak_key(&self, key: &[u8]) -> bool {
60 if key.is_empty() {
61 return true;
62 }
63
64 if key.iter().all(|&b| b == 0) {
66 return true;
67 }
68
69 if key.iter().all(|&b| b == 0xFF) {
71 return true;
72 }
73
74 let first = key[0];
76 if key.iter().all(|&b| b == first) {
77 return true;
78 }
79
80 false
81 }
82
83 fn has_sufficient_entropy(&self, key: &[u8]) -> bool {
85 if key.len() < 16 {
86 return false;
87 }
88
89 let mut seen = [false; 256];
91 let mut unique_count = 0;
92
93 for &byte in key {
94 if !seen[byte as usize] {
95 seen[byte as usize] = true;
96 unique_count += 1;
97 }
98 }
99
100 let min_unique = key.len() / 2;
102 unique_count >= min_unique
103 }
104
105 pub fn validate_password(&self, password: &str, min_length: usize) -> SecurityResult<()> {
107 if password.len() < min_length {
109 return Err(SecurityError::WeakPassword(format!(
110 "Password too short (minimum {} characters)",
111 min_length
112 )));
113 }
114
115 if password.len() > 128 {
116 return Err(SecurityError::WeakPassword(
117 "Password too long (maximum 128 characters)".to_string(),
118 ));
119 }
120
121 if self.is_common_password(password) {
123 return Err(SecurityError::WeakPassword(
124 "Common password detected".to_string(),
125 ));
126 }
127
128 if self.strict_mode {
130 self.check_password_complexity(password)?;
131 }
132
133 Ok(())
134 }
135
136 fn check_password_complexity(&self, password: &str) -> SecurityResult<()> {
138 let has_lowercase = password.chars().any(|c| c.is_lowercase());
139 let has_uppercase = password.chars().any(|c| c.is_uppercase());
140 let has_digit = password.chars().any(|c| c.is_ascii_digit());
141 let has_special = password.chars().any(|c| !c.is_alphanumeric());
142
143 let complexity_score = [has_lowercase, has_uppercase, has_digit, has_special]
144 .iter()
145 .filter(|&&x| x)
146 .count();
147
148 if complexity_score < 3 {
149 return Err(SecurityError::WeakPassword(
150 "Password must contain at least 3 of: lowercase, uppercase, digits, special characters".to_string(),
151 ));
152 }
153
154 Ok(())
155 }
156
157 fn is_common_password(&self, password: &str) -> bool {
159 const COMMON_PASSWORDS: &[&str] = &[
161 "password", "123456", "123456789", "12345678", "12345", "1234567", "password1",
162 "123123", "1234567890", "000000", "admin", "qwerty", "abc123", "letmein",
163 "welcome", "monkey", "dragon", "master", "sunshine", "princess",
164 ];
165
166 let lower = password.to_lowercase();
167 COMMON_PASSWORDS.contains(&lower.as_str())
168 }
169
170 pub fn hash_password(&self, password: &str) -> SecurityResult<String> {
172 use argon2::{
173 password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
174 Argon2,
175 };
176
177 let salt = SaltString::generate(&mut OsRng);
178 let argon2 = Argon2::default();
179
180 argon2
181 .hash_password(password.as_bytes(), &salt)
182 .map(|hash| hash.to_string())
183 .map_err(|e| SecurityError::CryptoError(format!("Password hashing failed: {}", e)))
184 }
185
186 pub fn verify_password(&self, password: &str, hash: &str) -> SecurityResult<bool> {
188 use argon2::{
189 password_hash::{PasswordHash, PasswordVerifier},
190 Argon2,
191 };
192
193 let parsed_hash = PasswordHash::new(hash)
194 .map_err(|e| SecurityError::CryptoError(format!("Invalid password hash: {}", e)))?;
195
196 Ok(Argon2::default()
197 .verify_password(password.as_bytes(), &parsed_hash)
198 .is_ok())
199 }
200
201 pub fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
203 if a.len() != b.len() {
204 return false;
205 }
206 constant_time_eq(a, b)
207 }
208}
209
210pub struct KeyValidator {
212 max_age_days: i64,
213}
214
215impl KeyValidator {
216 pub fn new(max_age_days: i64) -> Self {
218 Self { max_age_days }
219 }
220
221 pub fn default() -> Self {
223 Self::new(MAX_KEY_AGE_DAYS)
224 }
225
226 pub fn should_rotate(
228 &self,
229 created_at: chrono::DateTime<chrono::Utc>,
230 ) -> bool {
231 let age = chrono::Utc::now().signed_duration_since(created_at);
232 age.num_days() >= self.max_age_days
233 }
234
235 pub fn days_until_rotation(
237 &self,
238 created_at: chrono::DateTime<chrono::Utc>,
239 ) -> i64 {
240 let age = chrono::Utc::now().signed_duration_since(created_at);
241 self.max_age_days - age.num_days()
242 }
243
244 pub fn validate_metadata(
246 &self,
247 created_at: chrono::DateTime<chrono::Utc>,
248 algorithm: &str,
249 ) -> SecurityResult<()> {
250 if self.should_rotate(created_at) {
252 return Err(SecurityError::CryptoError(
253 "Key rotation required".to_string(),
254 ));
255 }
256
257 match algorithm {
259 "aes-256-gcm" | "chacha20-poly1305" => Ok(()),
260 _ => Err(SecurityError::CryptoError(format!(
261 "Unsupported algorithm: {}",
262 algorithm
263 ))),
264 }
265 }
266}
267
268pub struct SecureSecret {
270 inner: Secret<Zeroizing<Vec<u8>>>,
271}
272
273impl SecureSecret {
274 pub fn new(data: Vec<u8>) -> Self {
276 Self {
277 inner: Secret::new(Zeroizing::new(data)),
278 }
279 }
280
281 pub fn expose(&self) -> &[u8] {
283 self.inner.expose_secret()
284 }
285
286 pub fn len(&self) -> usize {
288 self.inner.expose_secret().len()
289 }
290
291 pub fn is_empty(&self) -> bool {
293 self.inner.expose_secret().is_empty()
294 }
295}
296
297impl Drop for SecureSecret {
298 fn drop(&mut self) {
299 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_key_validation() {
309 let validator = CryptoValidator::strict();
310
311 let valid_key = vec![1u8; 32];
313 assert!(validator.validate_key(&valid_key).is_ok());
314
315 let short_key = vec![1u8; 16];
317 assert!(validator.validate_key(&short_key).is_err());
318
319 let weak_key = vec![0u8; 32];
321 assert!(validator.validate_key(&weak_key).is_err());
322
323 let weak_key = vec![0xFFu8; 32];
325 assert!(validator.validate_key(&weak_key).is_err());
326 }
327
328 #[test]
329 fn test_password_validation() {
330 let validator = CryptoValidator::strict();
331
332 assert!(validator.validate_password("MyP@ssw0rd123!", 12).is_ok());
334
335 assert!(validator.validate_password("short", 12).is_err());
337
338 assert!(validator.validate_password("password123", 8).is_err());
340
341 assert!(validator.validate_password("allowercase", 12).is_err());
343 }
344
345 #[test]
346 fn test_password_hashing() {
347 let validator = CryptoValidator::new(false);
348
349 let password = "MySecurePassword123!";
350 let hash = validator.hash_password(password).unwrap();
351
352 assert!(validator.verify_password(password, &hash).unwrap());
354
355 assert!(!validator.verify_password("WrongPassword", &hash).unwrap());
357 }
358
359 #[test]
360 fn test_constant_time_compare() {
361 let validator = CryptoValidator::new(false);
362
363 let a = b"secret";
364 let b = b"secret";
365 let c = b"Secret";
366
367 assert!(validator.constant_time_compare(a, b));
368 assert!(!validator.constant_time_compare(a, c));
369 }
370
371 #[test]
372 fn test_key_rotation() {
373 let validator = KeyValidator::default();
374
375 let recent = chrono::Utc::now() - chrono::Duration::days(30);
377 assert!(!validator.should_rotate(recent));
378 assert!(validator.days_until_rotation(recent) > 0);
379
380 let old = chrono::Utc::now() - chrono::Duration::days(100);
382 assert!(validator.should_rotate(old));
383 assert!(validator.days_until_rotation(old) < 0);
384 }
385
386 #[test]
387 fn test_algorithm_validation() {
388 let validator = KeyValidator::default();
389 let now = chrono::Utc::now();
390
391 assert!(validator.validate_metadata(now, "aes-256-gcm").is_ok());
392 assert!(validator
393 .validate_metadata(now, "chacha20-poly1305")
394 .is_ok());
395 assert!(validator.validate_metadata(now, "md5").is_err());
396 }
397
398 #[test]
399 fn test_secure_secret() {
400 let secret = SecureSecret::new(vec![1, 2, 3, 4, 5]);
401 assert_eq!(secret.len(), 5);
402 assert!(!secret.is_empty());
403 assert_eq!(secret.expose(), &[1, 2, 3, 4, 5]);
404 }
405
406 #[test]
407 fn test_password_complexity() {
408 let validator = CryptoValidator::strict();
409
410 assert!(validator
412 .check_password_complexity("Abc123!@#")
413 .is_ok());
414
415 assert!(validator
417 .check_password_complexity("Abc123456")
418 .is_err());
419
420 assert!(validator
422 .check_password_complexity("abc123!@#")
423 .is_err());
424 }
425
426 #[test]
427 fn test_entropy_check() {
428 let validator = CryptoValidator::strict();
429
430 let good_key: Vec<u8> = (0..32).map(|i| (i * 7) as u8).collect();
432 assert!(validator.has_sufficient_entropy(&good_key));
433
434 let poor_key = vec![1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2];
436 assert!(!validator.has_sufficient_entropy(&poor_key));
437 }
438}