1use hmac::{Hmac, Mac};
2use sha2::{Digest, Sha256, Sha512};
14use sha3::Sha3_512;
15use std::sync::atomic::{AtomicBool, Ordering};
16use zeroize::Zeroize;
17
18static FIPS_MODE_ENABLED: AtomicBool = AtomicBool::new(true);
20
21#[derive(Debug, Clone, Copy, PartialEq)]
30pub enum FipsSecurityLevel {
31 Level1 = 1,
33 Level2 = 2,
35 Level3 = 3,
37 Level4 = 4,
39}
40
41#[derive(Debug, Clone)]
44pub enum FipsApprovedAlgorithm {
45 Sha256,
47 Sha512,
49 Sha3_512,
51 HmacSha256,
53 HmacSha512,
55 Aes256Gcm,
57}
58
59#[allow(dead_code)]
68pub struct FipsModule {
69 security_level: FipsSecurityLevel,
71 self_test_passed: bool,
73 version: String,
75}
76
77impl Default for FipsModule {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl FipsModule {
84 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 pub fn power_on_self_test(&mut self) -> Result<(), String> {
97 let sha256_result = self.test_sha256()?;
101
102 let sha512_result = self.test_sha512()?;
104
105 let sha3_512_result = self.test_sha3_512()?;
107
108 let hmac_sha256_result = self.test_hmac_sha256()?;
110
111 let rng_result = self.test_rng()?;
113
114 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 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 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 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 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 fn test_rng(&self) -> Result<bool, String> {
202 use aes_gcm::aead::rand_core::RngCore;
203 use aes_gcm::aead::OsRng;
204
205 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 if block_a == block_b {
213 return Err("FIPS RNG test failed: consecutive outputs are identical".to_string());
214 }
215
216 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 block_a.zeroize();
226 block_b.zeroize();
227
228 Ok(true)
229 }
230
231 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 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 pub fn is_fips_mode(&self) -> bool {
250 FIPS_MODE_ENABLED.load(Ordering::SeqCst)
251 }
252
253 pub fn enable_fips_mode() {
255 FIPS_MODE_ENABLED.store(true, Ordering::SeqCst);
256 }
257
258 pub fn disable_fips_mode() {
260 FIPS_MODE_ENABLED.store(false, Ordering::SeqCst);
261 }
262
263 pub fn security_level(&self) -> FipsSecurityLevel {
265 self.security_level
266 }
267
268 pub fn self_test_status(&self) -> bool {
270 self.self_test_passed
271 }
272}
273
274#[derive(Zeroize)]
277#[zeroize(drop)]
278pub struct SecureKey {
279 key_material: Vec<u8>,
280}
281
282impl SecureKey {
283 pub fn new(key_material: Vec<u8>) -> Self {
285 SecureKey { key_material }
286 }
287
288 pub fn as_bytes(&self) -> &[u8] {
290 &self.key_material
291 }
292}
293
294pub struct FipsHash;
296
297impl FipsHash {
298 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 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 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
320pub struct FipsHmac;
322
323impl FipsHmac {
324 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 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#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_fips_power_on_self_test() {
350 let mut module = FipsModule::new();
351 assert!(module.power_on_self_test().is_ok());
352 assert!(module.self_test_status());
353 }
354
355 #[test]
356 fn test_sha256_kat() {
357 let module = FipsModule::new();
358 assert!(module.test_sha256().is_ok());
359 }
360
361 #[test]
362 fn test_sha512_kat() {
363 let module = FipsModule::new();
364 assert!(module.test_sha512().is_ok());
365 }
366
367 #[test]
368 fn test_sha3_512_kat() {
369 let module = FipsModule::new();
370 assert!(module.test_sha3_512().is_ok());
371 }
372
373 #[test]
374 fn test_hmac_sha256_kat() {
375 let module = FipsModule::new();
376 assert!(module.test_hmac_sha256().is_ok());
377 }
378
379 #[test]
380 fn test_secure_key_zeroization() {
381 let key = SecureKey::new(vec![1, 2, 3, 4, 5]);
382 assert_eq!(key.as_bytes(), &[1, 2, 3, 4, 5]);
383 drop(key);
384 }
386
387 #[test]
388 fn test_fips_hash_sha512() {
389 let data = b"test data";
390 let hash = FipsHash::sha512(data);
391 assert_eq!(hash.len(), 64); }
393
394 #[test]
395 fn test_fips_hmac() {
396 let key = b"secret key";
397 let data = b"message";
398 let tag = FipsHmac::hmac_sha512(key, data).unwrap();
399 assert!(FipsHmac::verify_hmac_sha512(key, data, &tag).is_ok());
400 }
401}