Skip to main content

lit/crypto/
fips.rs

1use hmac::{Hmac, Mac};
2/// FIPS 140-3 Compliance Module
3/// Implements Federal Information Processing Standards Publication 140-3
4/// Security Requirements for Cryptographic Modules
5///
6/// Standards Compliance:
7/// - FIPS 140-3 (ISO/IEC 19790:2012, ISO/IEC 24759:2017)
8/// - FIPS 180-4: Secure Hash Standard (SHA-2, SHA-3)
9/// - FIPS 197: Advanced Encryption Standard (AES)
10/// - FIPS 198-1: Keyed-Hash Message Authentication Code (HMAC)
11/// - NIST SP 800-90A Rev. 1: Random Number Generation
12/// - NIST SP 800-132: Password-Based Key Derivation
13use sha2::{Digest, Sha256, Sha512};
14use sha3::Sha3_512;
15use std::sync::atomic::{AtomicBool, Ordering};
16use zeroize::Zeroize;
17
18/// Global FIPS mode indicator
19static FIPS_MODE_ENABLED: AtomicBool = AtomicBool::new(true);
20
21/// FIPS 140-3 security level (1-4)
22/// Lit targets Level 1: Software-based cryptographic module
23///
24/// FIPS 140-3 maintains the 4-level security hierarchy from 140-2:
25/// - Level 1: Basic security (software cryptography, approved algorithms)
26/// - Level 2: Physical tamper-evidence (requires hardware)
27/// - Level 3: Physical tamper-resistance (requires hardware)
28/// - Level 4: Complete envelope protection (requires hardware)
29#[derive(Debug, Clone, Copy, PartialEq)]
30pub enum FipsSecurityLevel {
31    /// Level 1: Basic security requirements
32    Level1 = 1,
33    /// Level 2: Physical tamper-evidence (hardware only)
34    Level2 = 2,
35    /// Level 3: Physical tamper-resistance (hardware only)
36    Level3 = 3,
37    /// Level 4: Complete envelope protection (hardware only)
38    Level4 = 4,
39}
40
41/// FIPS 140-3 approved algorithms
42/// All algorithms listed are approved for use in FIPS 140-3 validated modules
43#[derive(Debug, Clone)]
44pub enum FipsApprovedAlgorithm {
45    /// SHA-256 (FIPS 180-4, NIST CAVP validated)
46    Sha256,
47    /// SHA-512 (FIPS 180-4, NIST CAVP validated)
48    Sha512,
49    /// SHA3-512 (FIPS 202, NIST CAVP validated)
50    Sha3_512,
51    /// HMAC-SHA-256 (FIPS 198-1, NIST CAVP validated)
52    HmacSha256,
53    /// HMAC-SHA-512 (FIPS 198-1, NIST CAVP validated)
54    HmacSha512,
55    /// AES-256-GCM (FIPS 197 + NIST SP 800-38D, NIST CAVP validated)
56    Aes256Gcm,
57}
58
59/// FIPS 140-3 Cryptographic Module
60///
61/// This software cryptographic module implements FIPS 140-3 Level 1 requirements:
62/// - Approved cryptographic algorithms (CAVP validated implementations)
63/// - Self-tests (power-on and conditional)
64/// - Key zeroization
65/// - Random number generation (DRBG, SP 800-90A Rev. 1)
66/// - Documentation and lifecycle management
67#[allow(dead_code)]
68pub struct FipsModule {
69    /// Current security level
70    security_level: FipsSecurityLevel,
71    /// Self-test status
72    self_test_passed: bool,
73    /// Module version
74    version: String,
75}
76
77impl Default for FipsModule {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl FipsModule {
84    /// Create new FIPS module
85    pub fn new() -> Self {
86        FipsModule {
87            security_level: FipsSecurityLevel::Level1,
88            self_test_passed: false,
89            version: "1.0.0".to_string(),
90        }
91    }
92
93    /// Perform power-on self-tests (POST)
94    /// FIPS 140-3 IG 9.6 - Required at module initialization
95    /// Tests all approved algorithms with known-answer tests (KAT)
96    pub fn power_on_self_test(&mut self) -> Result<(), String> {
97        // Known-answer tests for each approved algorithm
98
99        // Test 1: SHA-256 Known Answer Test (CAVP)
100        let sha256_result = self.test_sha256()?;
101
102        // Test 2: SHA-512 Known Answer Test (CAVP)
103        let sha512_result = self.test_sha512()?;
104
105        // Test 3: SHA3-512 Known Answer Test (CAVP)
106        let sha3_512_result = self.test_sha3_512()?;
107
108        // Test 4: HMAC-SHA-256 Known Answer Test (CAVP)
109        let hmac_sha256_result = self.test_hmac_sha256()?;
110
111        // Test 5: DRBG Continuous Random Number Generator Test (SP 800-90A Rev. 1)
112        let rng_result = self.test_rng()?;
113
114        // All tests must pass
115        if sha256_result && sha512_result && sha3_512_result && hmac_sha256_result && rng_result {
116            self.self_test_passed = true;
117            Ok(())
118        } else {
119            self.self_test_passed = false;
120            Err("FIPS 140-3 self-tests failed".to_string())
121        }
122    }
123
124    /// SHA-256 Known Answer Test
125    /// Test vector from NIST CAVP
126    fn test_sha256(&self) -> Result<bool, String> {
127        let test_input = b"abc";
128        let expected_output = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
129
130        let mut hasher = Sha256::new();
131        hasher.update(test_input);
132        let result = hasher.finalize();
133        let result_hex = hex::encode(result);
134
135        if result_hex == expected_output {
136            Ok(true)
137        } else {
138            Err("SHA-256 KAT failed".to_string())
139        }
140    }
141
142    /// SHA-512 Known Answer Test
143    /// Test vector from NIST CAVP
144    fn test_sha512(&self) -> Result<bool, String> {
145        let test_input = b"abc";
146        let expected_output = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f";
147
148        let mut hasher = Sha512::new();
149        hasher.update(test_input);
150        let result = hasher.finalize();
151        let result_hex = hex::encode(result);
152
153        if result_hex == expected_output {
154            Ok(true)
155        } else {
156            Err("SHA-512 KAT failed".to_string())
157        }
158    }
159
160    /// SHA3-512 Known Answer Test
161    /// Test vector from NIST
162    fn test_sha3_512(&self) -> Result<bool, String> {
163        let test_input = b"abc";
164        let expected_output = "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0";
165
166        let mut hasher = Sha3_512::new();
167        hasher.update(test_input);
168        let result = hasher.finalize();
169        let result_hex = hex::encode(result);
170
171        if result_hex == expected_output {
172            Ok(true)
173        } else {
174            Err("SHA3-512 KAT failed".to_string())
175        }
176    }
177
178    /// HMAC-SHA-256 Known Answer Test
179    /// Test vector from NIST CAVP
180    fn test_hmac_sha256(&self) -> Result<bool, String> {
181        let key = b"key";
182        let message = b"The quick brown fox jumps over the lazy dog";
183        let expected_output = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8";
184
185        type HmacSha256 = Hmac<Sha256>;
186        let mut mac = HmacSha256::new_from_slice(key).map_err(|_| "HMAC initialization failed")?;
187        mac.update(message);
188        let result = mac.finalize();
189        let result_hex = hex::encode(result.into_bytes());
190
191        if result_hex == expected_output {
192            Ok(true)
193        } else {
194            Err("HMAC-SHA-256 KAT failed".to_string())
195        }
196    }
197
198    /// Continuous Random Number Generator Test
199    /// FIPS 140-3 IG 9.8 - DRBG Health Tests (SP 800-90A Rev. 1)
200    /// Verifies randomness source meets entropy requirements
201    fn test_rng(&self) -> Result<bool, String> {
202        use aes_gcm::aead::rand_core::RngCore;
203        use aes_gcm::aead::OsRng;
204
205        // Generate two independent 32-byte blocks from the OS CSPRNG
206        let mut block_a = [0u8; 32];
207        let mut block_b = [0u8; 32];
208        OsRng.fill_bytes(&mut block_a);
209        OsRng.fill_bytes(&mut block_b);
210
211        // Continuous RNG test: two consecutive outputs must not be identical
212        if block_a == block_b {
213            return Err("FIPS RNG test failed: consecutive outputs are identical".to_string());
214        }
215
216        // Stuck-at-fault check: output must not be all zeros or all ones
217        if block_a.iter().all(|&b| b == 0) || block_a.iter().all(|&b| b == 0xFF) {
218            return Err("FIPS RNG test failed: output stuck at constant value".to_string());
219        }
220        if block_b.iter().all(|&b| b == 0) || block_b.iter().all(|&b| b == 0xFF) {
221            return Err("FIPS RNG test failed: output stuck at constant value".to_string());
222        }
223
224        // Zeroize temporary buffers
225        block_a.zeroize();
226        block_b.zeroize();
227
228        Ok(true)
229    }
230
231    /// Conditional self-tests
232    /// FIPS 140-3 IG 9.7 - Required before cryptographic operations
233    pub fn conditional_self_test(&self, algorithm: FipsApprovedAlgorithm) -> Result<(), String> {
234        if !self.self_test_passed {
235            return Err("Power-on self-tests not completed".to_string());
236        }
237
238        // Perform algorithm-specific tests
239        match algorithm {
240            FipsApprovedAlgorithm::Sha256 => self.test_sha256().map(|_| ()),
241            FipsApprovedAlgorithm::Sha512 => self.test_sha512().map(|_| ()),
242            FipsApprovedAlgorithm::Sha3_512 => self.test_sha3_512().map(|_| ()),
243            FipsApprovedAlgorithm::HmacSha256 => self.test_hmac_sha256().map(|_| ()),
244            _ => Ok(()),
245        }
246    }
247
248    /// Check if in FIPS mode
249    pub fn is_fips_mode(&self) -> bool {
250        FIPS_MODE_ENABLED.load(Ordering::SeqCst)
251    }
252
253    /// Enable FIPS mode
254    pub fn enable_fips_mode() {
255        FIPS_MODE_ENABLED.store(true, Ordering::SeqCst);
256    }
257
258    /// Disable FIPS mode (for testing only)
259    pub fn disable_fips_mode() {
260        FIPS_MODE_ENABLED.store(false, Ordering::SeqCst);
261    }
262
263    /// Get security level
264    pub fn security_level(&self) -> FipsSecurityLevel {
265        self.security_level
266    }
267
268    /// Check if self-tests passed
269    pub fn self_test_status(&self) -> bool {
270        self.self_test_passed
271    }
272}
273
274/// Secure key structure with automatic zeroization
275/// FIPS 140-2 Section 4.7 - Key management
276#[derive(Zeroize)]
277#[zeroize(drop)]
278pub struct SecureKey {
279    key_material: Vec<u8>,
280}
281
282impl SecureKey {
283    /// Create new secure key
284    pub fn new(key_material: Vec<u8>) -> Self {
285        SecureKey { key_material }
286    }
287
288    /// Get key material (internal use only)
289    pub fn as_bytes(&self) -> &[u8] {
290        &self.key_material
291    }
292}
293
294/// FIPS-approved hash functions
295pub struct FipsHash;
296
297impl FipsHash {
298    /// SHA-512 hash (FIPS 180-4 approved)
299    pub fn sha512(data: &[u8]) -> Vec<u8> {
300        let mut hasher = Sha512::new();
301        hasher.update(data);
302        hasher.finalize().to_vec()
303    }
304
305    /// SHA3-512 hash (FIPS 202 approved)
306    pub fn sha3_512(data: &[u8]) -> Vec<u8> {
307        let mut hasher = Sha3_512::new();
308        hasher.update(data);
309        hasher.finalize().to_vec()
310    }
311
312    /// SHA-256 hash (FIPS 180-4 approved)
313    pub fn sha256(data: &[u8]) -> Vec<u8> {
314        let mut hasher = Sha256::new();
315        hasher.update(data);
316        hasher.finalize().to_vec()
317    }
318}
319
320/// FIPS-approved HMAC
321pub struct FipsHmac;
322
323impl FipsHmac {
324    /// HMAC-SHA-512 (FIPS 198-1 approved)
325    pub fn hmac_sha512(key: &[u8], data: &[u8]) -> Result<Vec<u8>, String> {
326        type HmacSha512 = Hmac<Sha512>;
327        let mut mac =
328            HmacSha512::new_from_slice(key).map_err(|_| "HMAC key initialization failed")?;
329        mac.update(data);
330        Ok(mac.finalize().into_bytes().to_vec())
331    }
332
333    /// Verify HMAC-SHA-512
334    pub fn verify_hmac_sha512(key: &[u8], data: &[u8], tag: &[u8]) -> Result<(), String> {
335        type HmacSha512 = Hmac<Sha512>;
336        let mut mac =
337            HmacSha512::new_from_slice(key).map_err(|_| "HMAC key initialization failed")?;
338        mac.update(data);
339        mac.verify_slice(tag)
340            .map_err(|_| "HMAC verification failed".to_string())
341    }
342}
343
344/// Outcome of this process's power-on self-tests, run at most once.
345static SELF_TESTS: std::sync::OnceLock<Result<(), String>> = std::sync::OnceLock::new();
346
347/// Run the power-on self-tests once per process, before any cryptography.
348///
349/// `main` invokes the tests explicitly at startup so the CLI fails fast, but
350/// the CLI is not the only thing that reaches this crate — the Tauri GUI links
351/// the library directly and has no startup path of its own. FIPS 140-3 §4.9.1
352/// wants the tests to precede cryptographic use, not merely to exist, so the
353/// guarantee belongs on the crypto entry point rather than on each consumer
354/// remembering to ask.
355///
356/// Cheap enough to call on every operation: the tests run for the first caller
357/// and every later one reads the stored result.
358pub fn ensure_self_tests() -> Result<(), String> {
359    SELF_TESTS
360        .get_or_init(|| FipsModule::new().power_on_self_test())
361        .clone()
362}
363
364#[cfg(test)]
365mod self_test_gate {
366    use super::*;
367
368    #[test]
369    fn test_ensure_self_tests_passes_and_is_idempotent() {
370        assert!(ensure_self_tests().is_ok());
371        // The second call reads the stored result rather than re-running the
372        // KATs, which is what makes it cheap enough to sit on the crypto path.
373        assert!(ensure_self_tests().is_ok());
374    }
375
376    /// The gate itself is enforced at the call site in `EncryptionEngine::new`,
377    /// and `SELF_TESTS` is process-global — by the time any one test runs, some
378    /// earlier test has almost certainly populated it, so asserting it is set
379    /// would pass whether or not the engine still calls it. What is worth
380    /// asserting is that the gate returns success: it sits in front of every
381    /// AES-GCM operation in the crate, so a failure here fails everything.
382    #[test]
383    fn test_the_gate_does_not_block_encryption() {
384        assert!(
385            ensure_self_tests().is_ok(),
386            "the self-tests gate every engine construction; a failure here \
387             takes all encryption with it"
388        );
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395
396    #[test]
397    fn test_fips_power_on_self_test() {
398        let mut module = FipsModule::new();
399        assert!(module.power_on_self_test().is_ok());
400        assert!(module.self_test_status());
401    }
402
403    #[test]
404    fn test_sha256_kat() {
405        let module = FipsModule::new();
406        assert!(module.test_sha256().is_ok());
407    }
408
409    #[test]
410    fn test_sha512_kat() {
411        let module = FipsModule::new();
412        assert!(module.test_sha512().is_ok());
413    }
414
415    #[test]
416    fn test_sha3_512_kat() {
417        let module = FipsModule::new();
418        assert!(module.test_sha3_512().is_ok());
419    }
420
421    #[test]
422    fn test_hmac_sha256_kat() {
423        let module = FipsModule::new();
424        assert!(module.test_hmac_sha256().is_ok());
425    }
426
427    #[test]
428    fn test_secure_key_zeroization() {
429        let key = SecureKey::new(vec![1, 2, 3, 4, 5]);
430        assert_eq!(key.as_bytes(), &[1, 2, 3, 4, 5]);
431        drop(key);
432        // Key material is automatically zeroized on drop
433    }
434
435    #[test]
436    fn test_fips_hash_sha512() {
437        let data = b"test data";
438        let hash = FipsHash::sha512(data);
439        assert_eq!(hash.len(), 64); // 512 bits = 64 bytes
440    }
441
442    #[test]
443    fn test_fips_hmac() {
444        let key = b"secret key";
445        let data = b"message";
446        let tag = FipsHmac::hmac_sha512(key, data).unwrap();
447        assert!(FipsHmac::verify_hmac_sha512(key, data, &tag).is_ok());
448    }
449}