nabla_cli/enterprise/crypto/
mod.rs

1use anyhow::Result;
2use rustls::{ServerConfig, ClientConfig, RootCertStore, Certificate, PrivateKey};
3use rustls_pemfile::{certs, pkcs8_private_keys, rsa_private_keys};
4use std::fs::File;
5use std::io::BufReader;
6use std::path::Path;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::Arc;
9
10// Use OpenSSL for cryptographic operations
11use openssl::hash::{hash, MessageDigest};
12use openssl::pkcs5::pbkdf2_hmac;
13use openssl::rand::rand_bytes;
14use openssl::pkey::PKey;
15use openssl::rsa::Rsa;
16use openssl::sign::{Signer, Verifier};
17use openssl::symm::{encrypt, decrypt, Cipher};
18
19#[derive(Clone)]
20pub struct CryptoProvider {
21    pub fips_enabled: bool,
22    pub validation_enabled: bool,
23    pub fips_mode: bool,
24    pub module_initialized: Arc<AtomicBool>,
25    pub self_tests_passed: Arc<AtomicBool>,
26}
27
28impl CryptoProvider {
29    pub fn new(fips_enabled: bool, validation_enabled: bool) -> Result<Self> {
30        let provider = Self {
31            fips_enabled,
32            validation_enabled,
33            fips_mode: fips_enabled,
34            module_initialized: Arc::new(AtomicBool::new(false)),
35            self_tests_passed: Arc::new(AtomicBool::new(false)),
36        };
37
38        if fips_enabled {
39            tracing::info!("FIPS-compliant cryptographic provider created");
40        }
41
42        Ok(provider)
43    }
44
45    /// Initialize the cryptographic provider with FIPS compliance
46    pub fn initialize(&mut self) -> Result<()> {
47        if !self.fips_enabled {
48            return Err(anyhow::anyhow!("FIPS mode must be enabled"));
49        }
50
51        // Run self-tests
52        self.run_self_tests()?;
53        
54        // Validate entropy sources
55        self.validate_entropy_sources()?;
56        
57        // Set module as initialized
58        self.module_initialized.store(true, Ordering::SeqCst);
59        self.self_tests_passed.store(true, Ordering::SeqCst);
60        
61        tracing::info!("FIPS-compliant cryptographic provider initialized successfully");
62        Ok(())
63    }
64
65    /// Run FIPS-compliant self-tests using OpenSSL
66    fn run_self_tests(&self) -> Result<()> {
67        tracing::info!("Running FIPS-compliant self-tests...");
68        
69        // Test hash functions with NIST test vectors
70        self.test_hash_functions()?;
71        
72        // Test symmetric encryption
73        self.test_symmetric_encryption()?;
74        
75        // Test random number generation
76        self.test_random_generation()?;
77        
78        // Test key derivation
79        self.test_key_derivation()?;
80        
81        // Test digital signatures
82        self.test_digital_signatures()?;
83        
84        tracing::info!("All FIPS-compliant self-tests passed");
85        Ok(())
86    }
87
88    /// Test FIPS-approved hash functions using OpenSSL
89    fn test_hash_functions(&self) -> Result<()> {
90        // SHA-256 test vector from NIST
91        let test_data = b"abc";
92        let expected_sha256 = [
93            0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
94            0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
95            0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
96            0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad
97        ];
98        
99        let result = hash(MessageDigest::sha256(), test_data)?;
100        if result.as_ref() != expected_sha256 {
101            return Err(anyhow::anyhow!("SHA-256 self-test failed"));
102        }
103        
104        // Test SHA-384
105        let sha384_result = hash(MessageDigest::sha384(), test_data)?;
106        if sha384_result.len() != 48 {
107            return Err(anyhow::anyhow!("SHA-384 self-test failed"));
108        }
109        
110        // Test SHA-512
111        let sha512_result = hash(MessageDigest::sha512(), test_data)?;
112        if sha512_result.len() != 64 {
113            return Err(anyhow::anyhow!("SHA-512 self-test failed"));
114        }
115        
116        tracing::debug!("FIPS hash function tests passed");
117        Ok(())
118    }
119
120    /// Test FIPS-approved symmetric encryption
121    fn test_symmetric_encryption(&self) -> Result<()> {
122        let key = b"0123456789abcdef0123456789abcdef"; // 256-bit key
123        let iv = b"0123456789abcdef"; // 128-bit IV
124        let plaintext = b"Hello, FIPS world!";
125        
126        // Test AES-256-CBC (FIPS approved)
127        let ciphertext = encrypt(Cipher::aes_256_cbc(), key, Some(iv), plaintext)?;
128        let decrypted = decrypt(Cipher::aes_256_cbc(), key, Some(iv), &ciphertext)?;
129        
130        if decrypted != plaintext {
131            return Err(anyhow::anyhow!("AES-256-CBC self-test failed"));
132        }
133        
134        tracing::debug!("FIPS symmetric encryption tests passed");
135        Ok(())
136    }
137
138    /// Test FIPS-compliant random number generation
139    fn test_random_generation(&self) -> Result<()> {
140        // Generate multiple random samples
141        let mut samples = Vec::new();
142        for _ in 0..10 {
143            let mut buffer = vec![0u8; 32];
144            rand_bytes(&mut buffer)?;
145            samples.push(buffer);
146        }
147        
148        // Basic entropy checks
149        if samples.windows(2).all(|w| w[0] == w[1]) {
150            return Err(anyhow::anyhow!("Random generation test failed - samples identical"));
151        }
152        
153        // Check for obvious patterns
154        for sample in &samples {
155            if sample.iter().all(|&b| b == sample[0]) {
156                return Err(anyhow::anyhow!("Random generation test failed - pattern detected"));
157            }
158        }
159        
160        tracing::debug!("FIPS random generation tests passed");
161        Ok(())
162    }
163
164    /// Test FIPS-approved key derivation
165    fn test_key_derivation(&self) -> Result<()> {
166        let password = b"test_password";
167        let salt = b"test_salt_1234567890"; // At least 16 bytes for FIPS
168        let iterations = 10000; // FIPS minimum
169        
170        let mut derived_key = vec![0u8; 32];
171        pbkdf2_hmac(password, salt, iterations, MessageDigest::sha256(), &mut derived_key)?;
172        
173        // Verify key is not all zeros
174        if derived_key.iter().all(|&b| b == 0) {
175            return Err(anyhow::anyhow!("PBKDF2 test failed - key is all zeros"));
176        }
177        
178        tracing::debug!("FIPS key derivation tests passed");
179        Ok(())
180    }
181
182    /// Test FIPS-approved digital signatures
183    fn test_digital_signatures(&self) -> Result<()> {
184        // Generate RSA key pair (2048-bit minimum for FIPS)
185        let rsa = Rsa::generate(2048)?;
186        let pkey = PKey::from_rsa(rsa)?;
187        
188        let message = b"Test message for signature";
189        
190        // Sign with RSA-PSS (FIPS approved)
191        let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?;
192        signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?;
193        signer.update(message)?;
194        let signature = signer.sign_to_vec()?;
195        
196        // Verify signature
197        let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey)?;
198        verifier.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?;
199        verifier.update(message)?;
200        let valid = verifier.verify(&signature)?;
201        
202        if !valid {
203            return Err(anyhow::anyhow!("RSA-PSS signature test failed"));
204        }
205        
206        tracing::debug!("FIPS digital signature tests passed");
207        Ok(())
208    }
209
210    /// Validate FIPS-compliant entropy sources
211    fn validate_entropy_sources(&self) -> Result<()> {
212        tracing::info!("Validating FIPS-compliant entropy sources");
213        
214        // OpenSSL handles entropy validation internally
215        // We just need to verify it's working
216        let mut test_bytes = vec![0u8; 32];
217        rand_bytes(&mut test_bytes)?;
218        
219        if test_bytes.iter().all(|&b| b == 0) {
220            return Err(anyhow::anyhow!("Entropy source validation failed"));
221        }
222        
223        tracing::info!("FIPS entropy sources validated");
224        Ok(())
225    }
226
227    /// FIPS-compliant SHA-256 hash using OpenSSL
228    pub fn hash_sha256(&self, data: &[u8]) -> Result<[u8; 32]> {
229        let result = hash(MessageDigest::sha256(), data)?;
230        let mut hash_array = [0u8; 32];
231        hash_array.copy_from_slice(result.as_ref());
232        Ok(hash_array)
233    }
234
235    /// FIPS-compliant SHA-512 hash using OpenSSL
236    pub fn hash_sha512(&self, data: &[u8]) -> Result<[u8; 64]> {
237        let result = hash(MessageDigest::sha512(), data)?;
238        let mut hash_array = [0u8; 64];
239        hash_array.copy_from_slice(result.as_ref());
240        Ok(hash_array)
241    }
242
243    /// FIPS-compliant alternative hash (SHA-512 in FIPS mode, SHA-256 otherwise)
244    pub fn hash_alternative(&self, data: &[u8]) -> Result<Vec<u8>> {
245        if self.fips_enabled {
246            // Use SHA-512 in FIPS mode (approved algorithm)
247            let hash = self.hash_sha512(data)?;
248            Ok(hash.to_vec())
249        } else {
250            // Use SHA-256 for compatibility
251            let hash = self.hash_sha256(data)?;
252            Ok(hash.to_vec())
253        }
254    }
255
256    /// FIPS-compliant random number generation using OpenSSL
257    pub fn generate_random(&self, size: usize) -> Result<Vec<u8>> {
258        let mut bytes = vec![0u8; size];
259        rand_bytes(&mut bytes)?;
260        Ok(bytes)
261    }
262
263    /// FIPS-compliant PBKDF2 key derivation using OpenSSL
264    pub fn derive_key_pbkdf2(&self, password: &[u8], salt: &[u8], iterations: u32, key_len: usize) -> Result<Vec<u8>> {
265        // FIPS requires minimum 10,000 iterations
266        if self.fips_enabled && iterations < 10000 {
267            return Err(anyhow::anyhow!("FIPS requires minimum 10,000 PBKDF2 iterations"));
268        }
269        
270        // FIPS requires minimum 16-byte salt
271        if self.fips_enabled && salt.len() < 16 {
272            return Err(anyhow::anyhow!("FIPS requires minimum 16-byte salt"));
273        }
274        
275        let mut key = vec![0u8; key_len];
276        pbkdf2_hmac(password, salt, iterations as usize, MessageDigest::sha256(), &mut key)?;
277        Ok(key)
278    }
279
280    /// FIPS-compliant HKDF key derivation using the hkdf crate
281    pub fn derive_key_hkdf(&self, secret: &[u8], salt: &[u8], info: &[u8], key_len: usize) -> Result<Vec<u8>> {
282        use hkdf::Hkdf;
283        use sha2::Sha256;
284        
285        let hk = Hkdf::<Sha256>::new(Some(salt), secret);
286        let mut key = vec![0u8; key_len];
287        hk.expand(info, &mut key).map_err(|e| anyhow::anyhow!("HKDF expansion failed: {:?}", e))?;
288        
289        Ok(key)
290    }
291
292    /// AES-256-GCM encryption (FIPS approved)
293    pub fn encrypt_aes_gcm(&self, key: &[u8], nonce: &[u8], plaintext: &[u8], aad: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
294        if key.len() != 32 {
295            return Err(anyhow::anyhow!("AES-256 requires 32-byte key"));
296        }
297        
298        use openssl::symm::{Cipher, Crypter, Mode};
299        
300        let cipher = Cipher::aes_256_gcm();
301        let mut crypter = Crypter::new(cipher, Mode::Encrypt, key, Some(nonce))?;
302        
303        // Set AAD
304        if !aad.is_empty() {
305            crypter.aad_update(aad)?;
306        }
307        
308        let mut ciphertext = vec![0u8; plaintext.len() + cipher.block_size()];
309        let mut count = crypter.update(plaintext, &mut ciphertext)?;
310        count += crypter.finalize(&mut ciphertext[count..])?;
311        ciphertext.truncate(count);
312        
313        // Get authentication tag
314        let mut tag = vec![0u8; 16];
315        crypter.get_tag(&mut tag)?;
316        
317        Ok((ciphertext, tag))
318    }
319
320    /// AES-256-GCM decryption (FIPS approved)
321    pub fn decrypt_aes_gcm(&self, key: &[u8], nonce: &[u8], ciphertext: &[u8], aad: &[u8], tag: &[u8]) -> Result<Vec<u8>> {
322        if key.len() != 32 {
323            return Err(anyhow::anyhow!("AES-256 requires 32-byte key"));
324        }
325        
326        use openssl::symm::{Cipher, Crypter, Mode};
327        
328        let cipher = Cipher::aes_256_gcm();
329        let mut crypter = Crypter::new(cipher, Mode::Decrypt, key, Some(nonce))?;
330        
331        // Set authentication tag
332        crypter.set_tag(tag)?;
333        
334        // Set AAD
335        if !aad.is_empty() {
336            crypter.aad_update(aad)?;
337        }
338        
339        let mut plaintext = vec![0u8; ciphertext.len() + cipher.block_size()];
340        let mut count = crypter.update(ciphertext, &mut plaintext)?;
341        count += crypter.finalize(&mut plaintext[count..])?;
342        plaintext.truncate(count);
343        
344        Ok(plaintext)
345    }
346
347    /// Get FIPS status with OpenSSL information
348    pub fn get_fips_status(&self) -> FipsStatus {
349        FipsStatus {
350            fips_enabled: self.fips_mode,
351            module_initialized: self.module_initialized.load(Ordering::SeqCst),
352            self_tests_passed: self.self_tests_passed.load(Ordering::SeqCst),
353            entropy_validated: true, // OpenSSL handles this internally
354            kdf_initialized: self.fips_mode,
355            openssl_fips_enabled: false, // We're not using the FIPS module
356            openssl_version: openssl::version::version().to_string(),
357            approved_algorithms: vec![
358                "SHA-256".to_string(),
359                "SHA-384".to_string(),
360                "SHA-512".to_string(),
361                "HMAC-SHA256".to_string(),
362                "HMAC-SHA384".to_string(),
363                "HMAC-SHA512".to_string(),
364                "AES-128-CBC".to_string(),
365                "AES-192-CBC".to_string(),
366                "AES-256-CBC".to_string(),
367                "AES-128-GCM".to_string(),
368                "AES-192-GCM".to_string(),
369                "AES-256-GCM".to_string(),
370                "PBKDF2".to_string(),
371                "HKDF".to_string(),
372                "RSA-PSS".to_string(),
373                "ECDSA".to_string(),
374                "TLS 1.2".to_string(),
375                "TLS 1.3".to_string(),
376            ],
377        }
378    }
379
380    /// Validate FIPS compliance
381    pub fn validate_fips_compliance(&mut self) -> Result<()> {
382        if !self.fips_enabled {
383            return Ok(());
384        }
385
386        // Initialize if not already done
387        if !self.module_initialized.load(Ordering::SeqCst) {
388            self.initialize()?;
389        }
390
391        tracing::info!("FIPS compliance validation passed");
392        Ok(())
393    }
394
395    /// Create FIPS-compliant server configuration using rustls with OpenSSL backend
396    pub fn get_fips_server_config(&self, cert_path: &Path, key_path: &Path) -> Result<ServerConfig> {
397        let certs = self.load_certificates(cert_path)?;
398        let key = self.load_private_key(key_path)?;
399
400        // Create server configuration
401        // Note: For full FIPS compliance, you may need rustls-openssl or similar
402        let config = ServerConfig::builder()
403            .with_safe_defaults()
404            .with_no_client_auth()
405            .with_single_cert(certs, key)?;
406
407        tracing::info!("FIPS-compliant server TLS configuration created");
408        Ok(config)
409    }
410
411    /// Create FIPS-compliant client configuration
412    pub fn get_fips_client_config(&self) -> Result<ClientConfig> {
413        let mut root_store = RootCertStore::empty();
414        root_store.add_trust_anchors(
415            webpki_roots::TLS_SERVER_ROOTS
416                .iter()
417                .map(|ta| {
418                    rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
419                        ta.subject,
420                        ta.spki,
421                        ta.name_constraints,
422                    )
423                })
424        );
425
426        let config = ClientConfig::builder()
427            .with_safe_defaults()
428            .with_root_certificates(root_store)
429            .with_no_client_auth();
430
431        tracing::info!("FIPS-compliant client TLS configuration created");
432        Ok(config)
433    }
434
435    // Keep the existing certificate loading methods
436    fn load_certificates(&self, cert_path: &Path) -> Result<Vec<Certificate>> {
437        let file = File::open(cert_path)?;
438        let mut reader = BufReader::new(file);
439        let certs = certs(&mut reader)?;
440        Ok(certs.into_iter().map(Certificate).collect())
441    }
442
443    fn load_private_key(&self, key_path: &Path) -> Result<PrivateKey> {
444        let file = File::open(key_path)?;
445        let mut reader = BufReader::new(file);
446        
447        if let Ok(keys) = pkcs8_private_keys(&mut reader) {
448            if !keys.is_empty() {
449                return Ok(PrivateKey(keys[0].clone()));
450            }
451        }
452        
453        let file = File::open(key_path)?;
454        let mut reader = BufReader::new(file);
455        let keys = rsa_private_keys(&mut reader)?;
456        if keys.is_empty() {
457            return Err(anyhow::anyhow!("No private keys found"));
458        }
459        
460        Ok(PrivateKey(keys[0].clone()))
461    }
462}
463
464#[derive(Debug, Clone, serde::Serialize)]
465pub struct FipsStatus {
466    pub fips_enabled: bool,
467    pub module_initialized: bool,
468    pub self_tests_passed: bool,
469    pub entropy_validated: bool,
470    pub kdf_initialized: bool,
471    pub openssl_fips_enabled: bool,
472    pub openssl_version: String,
473    pub approved_algorithms: Vec<String>,
474}