1#![allow(
4 clippy::must_use_candidate,
5 clippy::cast_lossless,
6 clippy::manual_clamp,
7 clippy::needless_return,
8 clippy::collapsible_if,
9 clippy::match_same_arms,
10 clippy::unreadable_literal,
11 clippy::missing_errors_doc
12)]
13
14#[cfg(feature = "alloc")]
21use alloc::{
22 boxed::Box,
23 vec::Vec,
24};
25
26use crate::kt128_expander::Kt128Expander;
27use crate::traits::{
28 EntropyConfig,
29 EntropySource,
30 EntropySourceType,
31};
32use crate::{
33 Error,
34 Result,
35};
36
37#[derive(Debug, Clone)]
43pub struct OsEntropySource {
44 platform: &'static str,
46 quality: f64,
48 max_per_call: Option<usize>,
50}
51
52impl OsEntropySource {
53 pub fn new() -> Self {
55 let platform = Self::detect_platform();
56 let quality = Self::estimate_platform_quality(platform);
57 Self {
58 platform,
59 quality,
60 max_per_call: None,
61 }
62 }
63
64 fn estimate_platform_quality(platform: &'static str) -> f64 {
66 match platform {
67 "Linux" => 0.95, "macOS" => 0.95, "Windows" => 0.95, "FreeBSD" => 0.95, "OpenBSD" => 0.95, "NetBSD" => 0.95, "WebAssembly" => 0.90, _ => 0.80, }
76 }
77
78 pub fn platform(&self) -> &'static str {
80 self.platform
81 }
82
83 fn detect_platform() -> &'static str {
85 #[cfg(target_os = "linux")]
86 return "Linux";
87 #[cfg(target_os = "macos")]
88 return "macOS";
89 #[cfg(target_os = "windows")]
90 return "Windows";
91 #[cfg(target_os = "freebsd")]
92 return "FreeBSD";
93 #[cfg(target_os = "openbsd")]
94 return "OpenBSD";
95 #[cfg(target_os = "netbsd")]
96 return "NetBSD";
97 #[cfg(target_arch = "wasm32")]
98 return "WebAssembly";
99 #[cfg(not(any(
100 target_os = "linux",
101 target_os = "macos",
102 target_os = "windows",
103 target_os = "freebsd",
104 target_os = "openbsd",
105 target_os = "netbsd",
106 target_arch = "wasm32"
107 )))]
108 return "Unknown";
109 }
110}
111
112impl EntropySource for OsEntropySource {
113 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
114 if let Some(max_per_call) = self.max_entropy_per_call() {
116 if dest.len() > max_per_call {
117 return Err(Error::entropy_source_unavailable(
118 "Requested entropy exceeds maximum per call",
119 ));
120 }
121 }
122
123 #[cfg(feature = "std")]
124 {
125 getrandom::fill(dest).map_err(|_| {
126 Error::platform_rng_failed_with_code(
127 self.platform,
128 -1,
129 "Failed to get entropy from OS",
130 )
131 })
132 }
133 #[cfg(not(feature = "std"))]
134 {
135 Err(Error::feature_not_available("OS entropy source", &["std"]))
136 }
137 }
138
139 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
140 if self.quality() < config.min_quality {
142 return Err(Error::entropy_source_unavailable(
143 "OS entropy source quality below required minimum",
144 ));
145 }
146
147 if let Some(max_per_call) = config.max_per_call {
149 if max_per_call > 256 {
150 return Err(Error::entropy_source_unavailable(
151 "Requested max_per_call exceeds OS entropy source limit",
152 ));
153 }
154 self.max_per_call = Some(max_per_call);
155 }
156
157 Ok(())
158 }
159
160 fn is_available(&self) -> bool {
161 #[cfg(feature = "std")]
162 {
163 true
164 }
165 #[cfg(not(feature = "std"))]
166 {
167 false
168 }
169 }
170
171 fn quality(&self) -> f64 {
172 self.quality
173 }
174
175 fn name(&self) -> &'static str {
176 match self.platform {
177 "Linux" => "Linux OS Entropy Source",
178 "macOS" => "macOS OS Entropy Source",
179 "Windows" => "Windows OS Entropy Source",
180 "FreeBSD" => "FreeBSD OS Entropy Source",
181 "OpenBSD" => "OpenBSD OS Entropy Source",
182 "NetBSD" => "NetBSD OS Entropy Source",
183 "WebAssembly" => "WebAssembly OS Entropy Source",
184 _ => "Unknown OS Entropy Source",
185 }
186 }
187
188 fn source_type(&self) -> EntropySourceType {
189 EntropySourceType::OperatingSystem
190 }
191
192 fn max_entropy_per_call(&self) -> Option<usize> {
193 self.max_per_call.or(Some(16384)) }
195}
196
197impl Default for OsEntropySource {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203#[derive(Debug, Clone)]
212pub struct HardwareEntropySource {
213 device: &'static str,
215 quality: f64,
217 available: bool,
219}
220
221impl HardwareEntropySource {
222 pub fn new() -> Self {
224 let (device, available) = Self::detect_hardware_rng();
225 Self {
226 device,
227 quality: if available { 0.99 } else { 0.0 },
228 available,
229 }
230 }
231
232 fn detect_hardware_rng() -> (&'static str, bool) {
234 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
235 {
236 if let Some(label) = crate::hardware_rng::probe_rdrand() {
237 return (label, true);
238 }
239 #[cfg(target_arch = "x86_64")]
240 {
241 return ("x86_64 (RDRAND unavailable)", false);
242 }
243 #[cfg(target_arch = "x86")]
244 {
245 return ("x86 (RDRAND unavailable)", false);
246 }
247 }
248 #[cfg(target_arch = "aarch64")]
249 {
250 return ("AArch64", false);
251 }
252 #[cfg(any(target_arch = "powerpc64", target_arch = "powerpc"))]
253 {
254 return ("PowerPC", false);
255 }
256 #[cfg(not(any(
257 target_arch = "x86",
258 target_arch = "x86_64",
259 target_arch = "aarch64",
260 target_arch = "powerpc64",
261 target_arch = "powerpc"
262 )))]
263 {
264 return ("Unknown", false);
265 }
266 }
267}
268
269impl EntropySource for HardwareEntropySource {
270 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
271 if !self.available {
272 return Err(Error::hardware_rng_failed(self.device));
273 }
274
275 if let Some(max_per_call) = self.max_entropy_per_call() {
277 if dest.len() > max_per_call {
278 return Err(Error::entropy_source_unavailable(
279 "Requested entropy exceeds hardware RNG maximum per call",
280 ));
281 }
282 }
283
284 crate::hardware_rng::fill_hw_cpu(dest, self.device)
285 }
286
287 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
288 if self.quality() < config.min_quality {
290 return Err(Error::entropy_source_unavailable(
291 "Hardware entropy source quality below required minimum",
292 ));
293 }
294
295 if let Some(max_per_call) = config.max_per_call {
297 if max_per_call > crate::hardware_rng::HW_RNG_MAX_PER_CALL {
298 return Err(Error::entropy_source_unavailable(
299 "Requested max_per_call exceeds hardware RNG limit",
300 ));
301 }
302 }
303
304 Ok(())
305 }
306
307 fn is_available(&self) -> bool {
308 self.available
309 }
310
311 fn quality(&self) -> f64 {
312 self.quality
313 }
314
315 fn name(&self) -> &'static str {
316 "Hardware RNG"
317 }
318
319 fn source_type(&self) -> EntropySourceType {
320 EntropySourceType::Hardware
321 }
322
323 fn max_entropy_per_call(&self) -> Option<usize> {
324 Some(crate::hardware_rng::HW_RNG_MAX_PER_CALL)
325 }
326}
327
328impl Default for HardwareEntropySource {
329 fn default() -> Self {
330 Self::new()
331 }
332}
333
334#[derive(Debug, Clone)]
348pub struct DeterministicEntropySource {
349 expander: Kt128Expander,
351 quality: f64,
353}
354
355impl DeterministicEntropySource {
356 #[must_use]
358 pub fn new(seed: [u8; 32]) -> Self {
359 Self {
360 expander: Kt128Expander::from_det_seed_32(seed),
361 quality: 0.0, }
363 }
364
365 #[must_use]
367 pub fn from_u64(seed: u64) -> Self {
368 Self {
369 expander: Kt128Expander::from_det_u64(seed),
370 quality: 0.0,
371 }
372 }
373
374 fn generate_deterministic_bytes(&mut self, dest: &mut [u8]) {
375 self.expander.fill_bytes(dest);
376 }
377}
378
379impl EntropySource for DeterministicEntropySource {
380 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
381 self.generate_deterministic_bytes(dest);
382 Ok(())
383 }
384
385 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
386 let _ = config;
389 Ok(())
390 }
391
392 fn is_available(&self) -> bool {
393 true
394 }
395
396 fn quality(&self) -> f64 {
397 self.quality
398 }
399
400 fn name(&self) -> &'static str {
401 "Deterministic Entropy Source"
402 }
403
404 fn source_type(&self) -> EntropySourceType {
405 EntropySourceType::Deterministic
406 }
407
408 fn max_entropy_per_call(&self) -> Option<usize> {
409 None }
411}
412
413#[cfg(feature = "alloc")]
419#[derive(Debug, Clone)]
420pub struct UserEntropySource {
421 entropy_data: Vec<u8>,
423 position: usize,
425 quality: f64,
427 max_per_call: Option<usize>,
429}
430
431#[cfg(feature = "alloc")]
432impl UserEntropySource {
433 pub fn new(entropy_data: Vec<u8>) -> Self {
435 Self {
436 quality: 0.8, entropy_data,
438 position: 0,
439 max_per_call: None, }
441 }
442
443 pub fn with_quality(entropy_data: Vec<u8>, quality: f64) -> Self {
445 Self {
446 quality: quality.max(0.0).min(1.0),
447 entropy_data,
448 position: 0,
449 max_per_call: None, }
451 }
452}
453
454#[cfg(feature = "alloc")]
455impl EntropySource for UserEntropySource {
456 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
457 if self.entropy_data.is_empty() {
458 return Err(Error::entropy_source_unavailable("User entropy source"));
459 }
460
461 if let Some(max_per_call) = self.max_per_call {
463 if dest.len() > max_per_call {
464 return Err(Error::entropy_source_unavailable(
465 "Requested entropy exceeds user entropy source maximum per call",
466 ));
467 }
468 }
469
470 for (i, byte) in dest.iter_mut().enumerate() {
471 let index = (self.position + i) % self.entropy_data.len();
472 *byte = self.entropy_data[index];
473 }
474
475 self.position = (self.position + dest.len()) % self.entropy_data.len();
476 Ok(())
477 }
478
479 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
480 if self.quality() < config.min_quality {
482 return Err(Error::entropy_source_unavailable(
483 "User entropy source quality below required minimum",
484 ));
485 }
486
487 if let Some(max_per_call) = config.max_per_call {
490 if max_per_call > 1024 {
491 return Err(Error::entropy_source_unavailable(
492 "Requested max_per_call exceeds reasonable limit for user entropy source",
493 ));
494 }
495 self.max_per_call = Some(max_per_call);
497 }
498
499 Ok(())
500 }
501
502 fn is_available(&self) -> bool {
503 !self.entropy_data.is_empty()
504 }
505
506 fn quality(&self) -> f64 {
507 self.quality
508 }
509
510 fn name(&self) -> &'static str {
511 "User Entropy Source"
512 }
513
514 fn source_type(&self) -> EntropySourceType {
515 EntropySourceType::User
516 }
517
518 fn max_entropy_per_call(&self) -> Option<usize> {
519 Some(self.entropy_data.len())
520 }
521}
522
523#[cfg(feature = "nist-drbg")]
535#[derive(Debug, Clone)]
536pub struct NistAes256CtrDrbg {
537 key: [u8; 32],
539 v: [u8; 16],
541 reseed_counter: i32,
543 quality: f64,
545}
546
547#[cfg(feature = "nist-drbg")]
548impl NistAes256CtrDrbg {
549 pub fn new() -> Self {
551 Self {
552 key: [0u8; 32],
553 v: [0u8; 16],
554 reseed_counter: 0,
555 quality: 1.0, }
557 }
558}
559
560#[cfg(feature = "nist-drbg")]
561impl Default for NistAes256CtrDrbg {
562 fn default() -> Self {
563 Self::new()
564 }
565}
566
567#[cfg(feature = "nist-drbg")]
568impl NistAes256CtrDrbg {
569 pub fn randombytes_init(&mut self, entropy_input: [u8; 48]) {
573 self.key = [0u8; 32];
574 self.v = [0u8; 16];
575 self.reseed_counter = 1i32;
576
577 Self::aes256_ctr_update(&mut Some(entropy_input), &mut self.key, &mut self.v);
578 self.reseed_counter = 1;
579 }
580
581 fn aes256_ecb(key: &[u8; 32], ctr: &[u8; 16], buffer: &mut [u8; 16]) {
585 use aes::cipher::{
586 BlockCipherEncrypt,
587 KeyInit,
588 };
589 use aes::{
590 Aes256,
591 Block,
592 };
593 let cipher = Aes256::new_from_slice(key).expect("32-byte key");
594 buffer.copy_from_slice(ctr);
595 let mut block = Block::from(*buffer);
596 cipher.encrypt_block(&mut block);
597 *buffer = block.into();
598 }
599
600 fn aes256_ctr_update(
602 provided_data: &mut Option<[u8; 48]>,
603 key: &mut [u8; 32],
604 v: &mut [u8; 16],
605 ) {
606 let mut temp = [[0u8; 16]; 3];
607
608 for tmp in &mut temp[0..3] {
609 let count = u128::from_be_bytes(*v);
610 let count_next = count.wrapping_add(1);
611 v.copy_from_slice(&count_next.to_be_bytes());
612
613 Self::aes256_ecb(key, v, tmp);
614 }
615
616 if let Some(d) = provided_data {
617 for j in 0..3 {
618 for i in 0..16 {
619 temp[j][i] ^= d[16 * j + i];
620 }
621 }
622 }
623
624 key[0..16].copy_from_slice(&temp[0]);
625 key[16..32].copy_from_slice(&temp[1]);
626 v.copy_from_slice(&temp[2]);
627 }
628
629 fn generate_bytes(&mut self, dest: &mut [u8]) {
631 for chunk in dest.chunks_mut(16) {
632 let count = u128::from_be_bytes(self.v);
633 let count_next = count.wrapping_add(1);
634 self.v.copy_from_slice(&count_next.to_be_bytes());
635
636 let mut block = [0u8; 16];
637 Self::aes256_ecb(&self.key, &self.v, &mut block);
638
639 chunk.copy_from_slice(&block[..chunk.len()]);
640 }
641
642 Self::aes256_ctr_update(&mut None, &mut self.key, &mut self.v);
643 self.reseed_counter += 1;
644 }
645}
646
647#[cfg(feature = "nist-drbg")]
648impl EntropySource for NistAes256CtrDrbg {
649 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
650 self.generate_bytes(dest);
651 Ok(())
652 }
653
654 fn is_available(&self) -> bool {
655 true
656 }
657
658 fn quality(&self) -> f64 {
659 self.quality
660 }
661
662 fn name(&self) -> &'static str {
663 "NIST AES256-CTR-DRBG"
664 }
665
666 fn source_type(&self) -> EntropySourceType {
667 EntropySourceType::Deterministic
668 }
669
670 fn max_entropy_per_call(&self) -> Option<usize> {
671 None }
673}
674
675#[cfg(feature = "alloc")]
680pub struct EntropySourceFactory;
681
682#[cfg(feature = "alloc")]
683impl EntropySourceFactory {
684 pub fn create_best_available() -> Result<Box<dyn EntropySource>> {
689 Self::create_best_available_with_config(&EntropyConfig::default())
690 }
691
692 pub fn create_best_available_with_config(
697 config: &EntropyConfig,
698 ) -> Result<Box<dyn EntropySource>> {
699 let mut hardware_source = HardwareEntropySource::new();
701 if hardware_source.is_available() {
702 if hardware_source.initialize(config).is_ok() {
703 return Ok(Box::new(hardware_source));
704 }
705 }
706
707 let mut os_source = OsEntropySource::new();
709 if os_source.is_available() {
710 if os_source.initialize(config).is_ok() {
711 return Ok(Box::new(os_source));
712 }
713 }
714
715 Err(Error::entropy_source_unavailable_with_context(
719 "secure entropy",
720 "no OS or hardware entropy source satisfied the configuration; weak fallbacks are disabled",
721 ))
722 }
723
724 pub fn create_os_entropy() -> Result<Box<dyn EntropySource>> {
726 Self::create_os_entropy_with_config(&EntropyConfig::default())
727 }
728
729 pub fn create_os_entropy_with_config(config: &EntropyConfig) -> Result<Box<dyn EntropySource>> {
731 let mut source = OsEntropySource::new();
732 if source.is_available() {
733 source.initialize(config)?;
734 Ok(Box::new(source))
735 } else {
736 Err(Error::entropy_source_unavailable("OS entropy source"))
737 }
738 }
739
740 pub fn create_hardware_entropy() -> Result<Box<dyn EntropySource>> {
742 Self::create_hardware_entropy_with_config(&EntropyConfig::default())
743 }
744
745 pub fn create_hardware_entropy_with_config(
747 config: &EntropyConfig,
748 ) -> Result<Box<dyn EntropySource>> {
749 let mut source = HardwareEntropySource::new();
750 if source.is_available() {
751 source.initialize(config)?;
752 Ok(Box::new(source))
753 } else {
754 Err(Error::entropy_source_unavailable("Hardware entropy source"))
755 }
756 }
757
758 pub fn create_deterministic_entropy(seed: [u8; 32]) -> Box<dyn EntropySource> {
760 Box::new(DeterministicEntropySource::new(seed))
761 }
762
763 pub fn create_deterministic_entropy_from_u64(seed: u64) -> Box<dyn EntropySource> {
765 Box::new(DeterministicEntropySource::from_u64(seed))
766 }
767
768 pub fn create_user_entropy(entropy_data: Vec<u8>) -> Box<dyn EntropySource> {
770 Box::new(UserEntropySource::new(entropy_data))
771 }
772
773 pub fn create_user_entropy_with_quality(
775 entropy_data: Vec<u8>,
776 quality: f64,
777 ) -> Box<dyn EntropySource> {
778 Box::new(UserEntropySource::with_quality(entropy_data, quality))
779 }
780
781 #[cfg(feature = "nist-drbg")]
783 pub fn create_nist_drbg_entropy(entropy_input: [u8; 48]) -> Box<dyn EntropySource> {
784 let mut drbg = NistAes256CtrDrbg::new();
785 drbg.randombytes_init(entropy_input);
786 Box::new(drbg)
787 }
788}
789
790#[cfg(test)]
791mod tests {
792 #[cfg(all(not(feature = "std"), feature = "alloc"))]
793 use alloc::format;
794 #[cfg(all(test, feature = "alloc"))]
795 use alloc::vec;
796
797 use super::*;
798 use crate::kt128_expander::{
799 KT128_DET_GOLDEN_U64_SEED_64,
800 KT128_DET_GOLDEN_ZERO_SEED_64,
801 };
802
803 #[test]
804 fn test_os_entropy_source_creation() {
805 let source = OsEntropySource::new();
806 assert!(!source.name().is_empty());
807 assert_eq!(source.source_type(), EntropySourceType::OperatingSystem);
808
809 let platform = source.platform();
811 assert!(!platform.is_empty());
812 assert!(source.name().contains(platform));
813
814 assert!(source.quality() > 0.0);
816 assert!(source.quality() <= 1.0);
817 }
818
819 #[test]
820 fn test_hardware_entropy_source_creation() {
821 let source = HardwareEntropySource::new();
822 assert!(!source.name().is_empty());
823 assert_eq!(source.source_type(), EntropySourceType::Hardware);
824 }
825
826 #[test]
827 #[cfg(all(feature = "std", target_arch = "x86_64"))]
828 fn test_hardware_entropy_rdrand_smoke_if_cpu_supports() {
829 if !std::arch::is_x86_feature_detected!("rdrand") {
830 return;
831 }
832 let mut source = HardwareEntropySource::new();
833 assert!(source.is_available());
834 source.initialize(&EntropyConfig::default()).unwrap();
835 let mut buf = [0u8; 64];
836 source.get_entropy(&mut buf).unwrap();
837 }
838
839 #[test]
840 fn test_deterministic_entropy_source_creation() {
841 let mut seed = [0u8; 32];
842 seed[..8].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
843 let source = DeterministicEntropySource::new(seed);
844 assert!(!source.name().is_empty());
845 assert_eq!(source.source_type(), EntropySourceType::Deterministic);
846 #[allow(clippy::float_cmp)]
847 {
848 assert_eq!(source.quality(), 0.0);
849 }
850 }
851
852 #[test]
853 fn test_deterministic_entropy_consistency() {
854 let seed = [42u8; 32];
855 let mut source1 = DeterministicEntropySource::new(seed);
856 let mut source2 = DeterministicEntropySource::new(seed);
857
858 let mut bytes1 = [0u8; 32];
859 let mut bytes2 = [0u8; 32];
860
861 source1.get_entropy(&mut bytes1).unwrap();
862 source2.get_entropy(&mut bytes2).unwrap();
863
864 assert_eq!(bytes1, bytes2);
865 }
866
867 #[test]
869 fn test_deterministic_entropy_golden_zero_seed() {
870 let mut source = DeterministicEntropySource::new([0u8; 32]);
871 let mut out = [0u8; 64];
872 source.get_entropy(&mut out).unwrap();
873 assert_eq!(out, KT128_DET_GOLDEN_ZERO_SEED_64);
874 }
875
876 #[test]
877 fn test_deterministic_entropy_golden_from_u64() {
878 let mut source = DeterministicEntropySource::from_u64(0x0123_4567_89AB_CDEF);
879 let mut out = [0u8; 64];
880 source.get_entropy(&mut out).unwrap();
881 assert_eq!(out, KT128_DET_GOLDEN_U64_SEED_64);
882 }
883
884 #[test]
885 #[cfg(feature = "alloc")]
886 fn test_user_entropy_source_creation() {
887 let entropy_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
888 let source = UserEntropySource::new(entropy_data);
889 assert!(!source.name().is_empty());
890 assert_eq!(source.source_type(), EntropySourceType::User);
891 }
892
893 #[test]
894 #[cfg(feature = "alloc")]
895 fn test_user_entropy_source_with_quality() {
896 let entropy_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
897 let source = UserEntropySource::with_quality(entropy_data, 0.9);
898 #[allow(clippy::float_cmp)]
899 {
900 assert_eq!(source.quality(), 0.9);
901 }
902 }
903
904 #[test]
905 #[cfg(feature = "alloc")]
906 fn test_user_entropy_source_cycling() {
907 let entropy_data = vec![1, 2, 3];
908 let mut source = UserEntropySource::new(entropy_data);
909
910 let config = EntropyConfig {
912 max_per_call: Some(6), ..Default::default()
914 };
915 source.initialize(&config).unwrap();
916
917 let mut bytes = [0u8; 6];
918 source.get_entropy(&mut bytes).unwrap();
919
920 assert_eq!(bytes, [1, 2, 3, 1, 2, 3]);
922 }
923
924 #[test]
925 #[cfg(feature = "alloc")]
926 fn test_entropy_source_factory_deterministic() {
927 let mut seed = [0u8; 32];
928 seed[..4].copy_from_slice(&[1, 2, 3, 4]);
929 let source = EntropySourceFactory::create_deterministic_entropy(seed);
930 assert_eq!(source.source_type(), EntropySourceType::Deterministic);
931 }
932
933 #[test]
934 #[cfg(all(feature = "alloc", feature = "nist-drbg"))]
935 fn test_nist_drbg_kat_vectors() {
936 use alloc::vec::Vec;
937
938 const RNG_REF1: &str = "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA19810F5392D076276EF41277C3AB6E94A4E3B7DCC104A05BB089D338BF55C72CAB375389A94BB920BD5D6DC9E7F2EC6FDE028B6F5724BB039F3652AD98DF8CE6C97013210B84BBE81388C3D141D61957C73BCDC5E5CD92525F46A2B757B03CAB5C337004A2DA35324A325713564DAE28F57ACC6DBE32A0726190BAA6B8A0A255AA1AD01E8DD569AA36D096256C420718A69D46D8DB1C6DD40606A0BE3C235BEFE623A90593F82D6A8F9F924E44E36BE87F7D26B8445966F9EE329C426C12521E85F6FD4ECD5D566BA0A3487125D79CC64";
940 const RNG_REF2: &str = "C17E034061ED5EA817C41D61636281E816F817DCF753A91D97C018FF82FBC9B1728FC66AF114B57978FB6082B70D285140B26725AA5F7BB4409820F67E2D656EDACA30B5BB12EB5249CC3809B188CF0CC95B5AE0EFE8FC5887152CB6601B4CCF9FC411894FA0C0264EB51A481D4D7074FDF065053030C8A92BFCDD06BF18C8489C38D03784FD63001830E5A385A4A37866693F5BDAB8A8A25B519DDBF2D28268601D95BEED647E430484A227C023B0297A282F06C91376433BDE5EC3ABBA8C06B830C26452EA2FA7EDEA8DCFE20EAFCF8980B3D5AECEF89DD861ACEC1F5F7CD2AE6B3CDE3C1D80A2830DD0B9E8468AFAD161981074BEB33DF1CDFF9A5214F9F0";
941
942 fn hex_to_bytes(hex: &str) -> Vec<u8> {
944 let mut bytes = Vec::new();
945 let mut chars = hex.chars().peekable();
946 while let (Some(c1), Some(c2)) = (chars.next(), chars.next()) {
947 let byte = u8::from_str_radix(&format!("{c1}{c2}"), 16).unwrap();
948 bytes.push(byte);
949 }
950 bytes
951 }
952
953 let mut entropy_input = [0u8; 48];
954 for (i, byte) in entropy_input.iter_mut().enumerate().take(48) {
955 *byte = u8::try_from(i).expect("i < 48, so conversion to u8 is safe");
956 }
957
958 let mut drbg = NistAes256CtrDrbg::new();
959 drbg.randombytes_init(entropy_input);
960
961 let mut data = [0u8; 256];
963 drbg.get_entropy(&mut data).unwrap();
964 let ref1 = hex_to_bytes(RNG_REF1);
965 assert_eq!(data.as_slice(), ref1.as_slice());
966
967 drbg.get_entropy(&mut data).unwrap();
969 let ref2 = hex_to_bytes(RNG_REF2);
970 assert_eq!(data.as_slice(), ref2.as_slice());
971 }
972
973 #[test]
974 #[cfg(feature = "alloc")]
975 fn test_entropy_source_factory_user() {
976 let entropy_data = vec![1, 2, 3, 4, 5];
977 let source = EntropySourceFactory::create_user_entropy(entropy_data);
978 assert_eq!(source.source_type(), EntropySourceType::User);
979 }
980
981 #[test]
982 fn test_entropy_config_validation() {
983 let config = EntropyConfig {
984 min_quality: 0.9, max_per_call: Some(32),
986 ..Default::default()
987 };
988
989 let mut os_source = OsEntropySource::new();
991 assert!(os_source.initialize(&config).is_ok());
993
994 let config2 = EntropyConfig {
996 min_quality: 0.99,
997 ..config
998 };
999 let mut os_source2 = OsEntropySource::new();
1000 assert!(os_source2.initialize(&config2).is_err());
1002 }
1003
1004 #[test]
1005 fn test_entropy_config_max_per_call() {
1006 let config = EntropyConfig {
1007 max_per_call: Some(16),
1008 ..Default::default()
1009 };
1010
1011 let mut os_source = OsEntropySource::new();
1012 os_source.initialize(&config).unwrap();
1013
1014 let mut buffer = [0u8; 32]; assert!(os_source.get_entropy(&mut buffer).is_err());
1017 }
1018
1019 #[test]
1020 #[cfg(feature = "alloc")]
1021 fn test_entropy_source_factory_with_config() {
1022 let config = EntropyConfig {
1023 min_quality: 0.8,
1024 max_per_call: Some(64),
1025 ..Default::default()
1026 };
1027
1028 let result = EntropySourceFactory::create_best_available_with_config(&config);
1030 let _ = result;
1032 }
1033
1034 #[test]
1035 #[cfg(any(feature = "std", feature = "alloc"))]
1036 fn test_platform_specific_error_messages() {
1037 let mut os_source = OsEntropySource::new();
1038 let platform = os_source.platform();
1039
1040 let config = EntropyConfig {
1042 min_quality: 1.0, ..Default::default()
1044 };
1045
1046 let result = os_source.initialize(&config);
1047 assert!(result.is_err());
1048 if let Err(error) = result {
1049 let error_msg = format!("{error}");
1050 assert!(error_msg.contains("quality below required minimum"));
1051 }
1052
1053 let config2 = EntropyConfig {
1055 max_per_call: Some(1000), ..Default::default()
1057 };
1058
1059 let result2 = os_source.initialize(&config2);
1060 assert!(result2.is_err());
1061 if let Err(error) = result2 {
1062 let error_msg = format!("{error}");
1063 assert!(error_msg.contains("exceeds OS entropy source limit"));
1064 }
1065
1066 assert!(!platform.is_empty());
1068 assert!(os_source.name().contains(platform));
1069 }
1070}