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 rand_chacha::ChaCha20Rng;
27use rand_core::{
28 Rng,
29 SeedableRng,
30};
31
32use crate::traits::{
33 EntropyConfig,
34 EntropySource,
35 EntropySourceType,
36};
37use crate::{
38 Error,
39 Result,
40};
41
42#[derive(Debug, Clone)]
48pub struct OsEntropySource {
49 platform: &'static str,
51 quality: f64,
53 max_per_call: Option<usize>,
55}
56
57impl OsEntropySource {
58 pub fn new() -> Self {
60 let platform = Self::detect_platform();
61 let quality = Self::estimate_platform_quality(platform);
62 Self {
63 platform,
64 quality,
65 max_per_call: None,
66 }
67 }
68
69 fn estimate_platform_quality(platform: &'static str) -> f64 {
71 match platform {
72 "Linux" => 0.95, "macOS" => 0.95, "Windows" => 0.95, "FreeBSD" => 0.95, "OpenBSD" => 0.95, "NetBSD" => 0.95, "WebAssembly" => 0.90, _ => 0.80, }
81 }
82
83 pub fn platform(&self) -> &'static str {
85 self.platform
86 }
87
88 fn detect_platform() -> &'static str {
90 #[cfg(target_os = "linux")]
91 return "Linux";
92 #[cfg(target_os = "macos")]
93 return "macOS";
94 #[cfg(target_os = "windows")]
95 return "Windows";
96 #[cfg(target_os = "freebsd")]
97 return "FreeBSD";
98 #[cfg(target_os = "openbsd")]
99 return "OpenBSD";
100 #[cfg(target_os = "netbsd")]
101 return "NetBSD";
102 #[cfg(target_arch = "wasm32")]
103 return "WebAssembly";
104 #[cfg(not(any(
105 target_os = "linux",
106 target_os = "macos",
107 target_os = "windows",
108 target_os = "freebsd",
109 target_os = "openbsd",
110 target_os = "netbsd",
111 target_arch = "wasm32"
112 )))]
113 return "Unknown";
114 }
115}
116
117impl EntropySource for OsEntropySource {
118 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
119 if let Some(max_per_call) = self.max_entropy_per_call() {
121 if dest.len() > max_per_call {
122 return Err(Error::entropy_source_unavailable(
123 "Requested entropy exceeds maximum per call",
124 ));
125 }
126 }
127
128 #[cfg(feature = "std")]
129 {
130 getrandom::fill(dest).map_err(|_| {
131 Error::platform_rng_failed_with_code(
132 self.platform,
133 -1,
134 "Failed to get entropy from OS",
135 )
136 })
137 }
138 #[cfg(not(feature = "std"))]
139 {
140 Err(Error::feature_not_available("OS entropy source", &["std"]))
141 }
142 }
143
144 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
145 if self.quality() < config.min_quality {
147 return Err(Error::entropy_source_unavailable(
148 "OS entropy source quality below required minimum",
149 ));
150 }
151
152 if let Some(max_per_call) = config.max_per_call {
154 if max_per_call > 256 {
155 return Err(Error::entropy_source_unavailable(
156 "Requested max_per_call exceeds OS entropy source limit",
157 ));
158 }
159 self.max_per_call = Some(max_per_call);
160 }
161
162 Ok(())
163 }
164
165 fn is_available(&self) -> bool {
166 #[cfg(feature = "std")]
167 {
168 true
169 }
170 #[cfg(not(feature = "std"))]
171 {
172 false
173 }
174 }
175
176 fn quality(&self) -> f64 {
177 self.quality
178 }
179
180 fn name(&self) -> &'static str {
181 match self.platform {
182 "Linux" => "Linux OS Entropy Source",
183 "macOS" => "macOS OS Entropy Source",
184 "Windows" => "Windows OS Entropy Source",
185 "FreeBSD" => "FreeBSD OS Entropy Source",
186 "OpenBSD" => "OpenBSD OS Entropy Source",
187 "NetBSD" => "NetBSD OS Entropy Source",
188 "WebAssembly" => "WebAssembly OS Entropy Source",
189 _ => "Unknown OS Entropy Source",
190 }
191 }
192
193 fn source_type(&self) -> EntropySourceType {
194 EntropySourceType::OperatingSystem
195 }
196
197 fn max_entropy_per_call(&self) -> Option<usize> {
198 self.max_per_call.or(Some(16384)) }
200}
201
202impl Default for OsEntropySource {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208#[derive(Debug, Clone)]
217pub struct HardwareEntropySource {
218 device: &'static str,
220 quality: f64,
222 available: bool,
224}
225
226impl HardwareEntropySource {
227 pub fn new() -> Self {
229 let (device, available) = Self::detect_hardware_rng();
230 Self {
231 device,
232 quality: if available { 0.99 } else { 0.0 },
233 available,
234 }
235 }
236
237 fn detect_hardware_rng() -> (&'static str, bool) {
239 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
240 {
241 if let Some(label) = crate::hardware_rng::probe_rdrand() {
242 return (label, true);
243 }
244 #[cfg(target_arch = "x86_64")]
245 {
246 return ("x86_64 (RDRAND unavailable)", false);
247 }
248 #[cfg(target_arch = "x86")]
249 {
250 return ("x86 (RDRAND unavailable)", false);
251 }
252 }
253 #[cfg(target_arch = "aarch64")]
254 {
255 return ("AArch64", false);
256 }
257 #[cfg(any(target_arch = "powerpc64", target_arch = "powerpc"))]
258 {
259 return ("PowerPC", false);
260 }
261 #[cfg(not(any(
262 target_arch = "x86",
263 target_arch = "x86_64",
264 target_arch = "aarch64",
265 target_arch = "powerpc64",
266 target_arch = "powerpc"
267 )))]
268 {
269 return ("Unknown", false);
270 }
271 }
272}
273
274impl EntropySource for HardwareEntropySource {
275 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
276 if !self.available {
277 return Err(Error::hardware_rng_failed(self.device));
278 }
279
280 if let Some(max_per_call) = self.max_entropy_per_call() {
282 if dest.len() > max_per_call {
283 return Err(Error::entropy_source_unavailable(
284 "Requested entropy exceeds hardware RNG maximum per call",
285 ));
286 }
287 }
288
289 crate::hardware_rng::fill_hw_cpu(dest, self.device)
290 }
291
292 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
293 if self.quality() < config.min_quality {
295 return Err(Error::entropy_source_unavailable(
296 "Hardware entropy source quality below required minimum",
297 ));
298 }
299
300 if let Some(max_per_call) = config.max_per_call {
302 if max_per_call > crate::hardware_rng::HW_RNG_MAX_PER_CALL {
303 return Err(Error::entropy_source_unavailable(
304 "Requested max_per_call exceeds hardware RNG limit",
305 ));
306 }
307 }
308
309 Ok(())
310 }
311
312 fn is_available(&self) -> bool {
313 self.available
314 }
315
316 fn quality(&self) -> f64 {
317 self.quality
318 }
319
320 fn name(&self) -> &'static str {
321 "Hardware RNG"
322 }
323
324 fn source_type(&self) -> EntropySourceType {
325 EntropySourceType::Hardware
326 }
327
328 fn max_entropy_per_call(&self) -> Option<usize> {
329 Some(crate::hardware_rng::HW_RNG_MAX_PER_CALL)
330 }
331}
332
333impl Default for HardwareEntropySource {
334 fn default() -> Self {
335 Self::new()
336 }
337}
338
339#[derive(Debug, Clone)]
354pub struct DeterministicEntropySource {
355 rng: ChaCha20Rng,
357 quality: f64,
359}
360
361impl DeterministicEntropySource {
362 #[must_use]
364 pub fn new(seed: [u8; 32]) -> Self {
365 Self {
366 rng: ChaCha20Rng::from_seed(seed),
367 quality: 0.0, }
369 }
370
371 fn generate_deterministic_bytes(&mut self, dest: &mut [u8]) {
372 self.rng.fill_bytes(dest);
373 }
374}
375
376impl EntropySource for DeterministicEntropySource {
377 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
378 self.generate_deterministic_bytes(dest);
379 Ok(())
380 }
381
382 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
383 let _ = config;
386 Ok(())
387 }
388
389 fn is_available(&self) -> bool {
390 true
391 }
392
393 fn quality(&self) -> f64 {
394 self.quality
395 }
396
397 fn name(&self) -> &'static str {
398 "Deterministic Entropy Source"
399 }
400
401 fn source_type(&self) -> EntropySourceType {
402 EntropySourceType::Deterministic
403 }
404
405 fn max_entropy_per_call(&self) -> Option<usize> {
406 None }
408}
409
410#[cfg(feature = "alloc")]
416#[derive(Debug, Clone)]
417pub struct UserEntropySource {
418 entropy_data: Vec<u8>,
420 position: usize,
422 quality: f64,
424 max_per_call: Option<usize>,
426}
427
428#[cfg(feature = "alloc")]
429impl UserEntropySource {
430 pub fn new(entropy_data: Vec<u8>) -> Self {
432 Self {
433 quality: 0.8, entropy_data,
435 position: 0,
436 max_per_call: None, }
438 }
439
440 pub fn with_quality(entropy_data: Vec<u8>, quality: f64) -> Self {
442 Self {
443 quality: quality.max(0.0).min(1.0),
444 entropy_data,
445 position: 0,
446 max_per_call: None, }
448 }
449}
450
451#[cfg(feature = "alloc")]
452impl EntropySource for UserEntropySource {
453 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
454 if self.entropy_data.is_empty() {
455 return Err(Error::entropy_source_unavailable("User entropy source"));
456 }
457
458 if let Some(max_per_call) = self.max_per_call {
460 if dest.len() > max_per_call {
461 return Err(Error::entropy_source_unavailable(
462 "Requested entropy exceeds user entropy source maximum per call",
463 ));
464 }
465 }
466
467 for (i, byte) in dest.iter_mut().enumerate() {
468 let index = (self.position + i) % self.entropy_data.len();
469 *byte = self.entropy_data[index];
470 }
471
472 self.position = (self.position + dest.len()) % self.entropy_data.len();
473 Ok(())
474 }
475
476 fn initialize(&mut self, config: &EntropyConfig) -> Result<()> {
477 if self.quality() < config.min_quality {
479 return Err(Error::entropy_source_unavailable(
480 "User entropy source quality below required minimum",
481 ));
482 }
483
484 if let Some(max_per_call) = config.max_per_call {
487 if max_per_call > 1024 {
488 return Err(Error::entropy_source_unavailable(
489 "Requested max_per_call exceeds reasonable limit for user entropy source",
490 ));
491 }
492 self.max_per_call = Some(max_per_call);
494 }
495
496 Ok(())
497 }
498
499 fn is_available(&self) -> bool {
500 !self.entropy_data.is_empty()
501 }
502
503 fn quality(&self) -> f64 {
504 self.quality
505 }
506
507 fn name(&self) -> &'static str {
508 "User Entropy Source"
509 }
510
511 fn source_type(&self) -> EntropySourceType {
512 EntropySourceType::User
513 }
514
515 fn max_entropy_per_call(&self) -> Option<usize> {
516 Some(self.entropy_data.len())
517 }
518}
519
520#[cfg(feature = "nist-drbg")]
532#[derive(Debug, Clone)]
533pub struct NistAes256CtrDrbg {
534 key: [u8; 32],
536 v: [u8; 16],
538 reseed_counter: i32,
540 quality: f64,
542}
543
544#[cfg(feature = "nist-drbg")]
545impl NistAes256CtrDrbg {
546 pub fn new() -> Self {
548 Self {
549 key: [0u8; 32],
550 v: [0u8; 16],
551 reseed_counter: 0,
552 quality: 1.0, }
554 }
555}
556
557#[cfg(feature = "nist-drbg")]
558impl Default for NistAes256CtrDrbg {
559 fn default() -> Self {
560 Self::new()
561 }
562}
563
564#[cfg(feature = "nist-drbg")]
565impl NistAes256CtrDrbg {
566 pub fn randombytes_init(&mut self, entropy_input: [u8; 48]) {
570 self.key = [0u8; 32];
571 self.v = [0u8; 16];
572 self.reseed_counter = 1i32;
573
574 Self::aes256_ctr_update(&mut Some(entropy_input), &mut self.key, &mut self.v);
575 self.reseed_counter = 1;
576 }
577
578 fn aes256_ecb(key: &[u8; 32], ctr: &[u8; 16], buffer: &mut [u8; 16]) {
582 use aes::cipher::{
583 BlockCipherEncrypt,
584 KeyInit,
585 };
586 use aes::{
587 Aes256,
588 Block,
589 };
590 let cipher = Aes256::new_from_slice(key).expect("32-byte key");
591 buffer.copy_from_slice(ctr);
592 let mut block = Block::from(*buffer);
593 cipher.encrypt_block(&mut block);
594 *buffer = block.into();
595 }
596
597 fn aes256_ctr_update(
599 provided_data: &mut Option<[u8; 48]>,
600 key: &mut [u8; 32],
601 v: &mut [u8; 16],
602 ) {
603 let mut temp = [[0u8; 16]; 3];
604
605 for tmp in &mut temp[0..3] {
606 let count = u128::from_be_bytes(*v);
607 let count_next = count.wrapping_add(1);
608 v.copy_from_slice(&count_next.to_be_bytes());
609
610 Self::aes256_ecb(key, v, tmp);
611 }
612
613 if let Some(d) = provided_data {
614 for j in 0..3 {
615 for i in 0..16 {
616 temp[j][i] ^= d[16 * j + i];
617 }
618 }
619 }
620
621 key[0..16].copy_from_slice(&temp[0]);
622 key[16..32].copy_from_slice(&temp[1]);
623 v.copy_from_slice(&temp[2]);
624 }
625
626 fn generate_bytes(&mut self, dest: &mut [u8]) {
628 for chunk in dest.chunks_mut(16) {
629 let count = u128::from_be_bytes(self.v);
630 let count_next = count.wrapping_add(1);
631 self.v.copy_from_slice(&count_next.to_be_bytes());
632
633 let mut block = [0u8; 16];
634 Self::aes256_ecb(&self.key, &self.v, &mut block);
635
636 chunk.copy_from_slice(&block[..chunk.len()]);
637 }
638
639 Self::aes256_ctr_update(&mut None, &mut self.key, &mut self.v);
640 self.reseed_counter += 1;
641 }
642}
643
644#[cfg(feature = "nist-drbg")]
645impl EntropySource for NistAes256CtrDrbg {
646 fn get_entropy(&mut self, dest: &mut [u8]) -> Result<()> {
647 self.generate_bytes(dest);
648 Ok(())
649 }
650
651 fn is_available(&self) -> bool {
652 true
653 }
654
655 fn quality(&self) -> f64 {
656 self.quality
657 }
658
659 fn name(&self) -> &'static str {
660 "NIST AES256-CTR-DRBG"
661 }
662
663 fn source_type(&self) -> EntropySourceType {
664 EntropySourceType::Deterministic
665 }
666
667 fn max_entropy_per_call(&self) -> Option<usize> {
668 None }
670}
671
672#[cfg(feature = "alloc")]
677pub struct EntropySourceFactory;
678
679#[cfg(feature = "alloc")]
680impl EntropySourceFactory {
681 pub fn create_best_available() -> Result<Box<dyn EntropySource>> {
686 Self::create_best_available_with_config(&EntropyConfig::default())
687 }
688
689 pub fn create_best_available_with_config(
694 config: &EntropyConfig,
695 ) -> Result<Box<dyn EntropySource>> {
696 let mut hardware_source = HardwareEntropySource::new();
698 if hardware_source.is_available() {
699 if hardware_source.initialize(config).is_ok() {
700 return Ok(Box::new(hardware_source));
701 }
702 }
703
704 let mut os_source = OsEntropySource::new();
706 if os_source.is_available() {
707 if os_source.initialize(config).is_ok() {
708 return Ok(Box::new(os_source));
709 }
710 }
711
712 Err(Error::entropy_source_unavailable_with_context(
716 "secure entropy",
717 "no OS or hardware entropy source satisfied the configuration; weak fallbacks are disabled",
718 ))
719 }
720
721 pub fn create_os_entropy() -> Result<Box<dyn EntropySource>> {
723 Self::create_os_entropy_with_config(&EntropyConfig::default())
724 }
725
726 pub fn create_os_entropy_with_config(config: &EntropyConfig) -> Result<Box<dyn EntropySource>> {
728 let mut source = OsEntropySource::new();
729 if source.is_available() {
730 source.initialize(config)?;
731 Ok(Box::new(source))
732 } else {
733 Err(Error::entropy_source_unavailable("OS entropy source"))
734 }
735 }
736
737 pub fn create_hardware_entropy() -> Result<Box<dyn EntropySource>> {
739 Self::create_hardware_entropy_with_config(&EntropyConfig::default())
740 }
741
742 pub fn create_hardware_entropy_with_config(
744 config: &EntropyConfig,
745 ) -> Result<Box<dyn EntropySource>> {
746 let mut source = HardwareEntropySource::new();
747 if source.is_available() {
748 source.initialize(config)?;
749 Ok(Box::new(source))
750 } else {
751 Err(Error::entropy_source_unavailable("Hardware entropy source"))
752 }
753 }
754
755 pub fn create_deterministic_entropy(seed: [u8; 32]) -> Box<dyn EntropySource> {
757 Box::new(DeterministicEntropySource::new(seed))
758 }
759
760 pub fn create_user_entropy(entropy_data: Vec<u8>) -> Box<dyn EntropySource> {
762 Box::new(UserEntropySource::new(entropy_data))
763 }
764
765 pub fn create_user_entropy_with_quality(
767 entropy_data: Vec<u8>,
768 quality: f64,
769 ) -> Box<dyn EntropySource> {
770 Box::new(UserEntropySource::with_quality(entropy_data, quality))
771 }
772
773 #[cfg(feature = "nist-drbg")]
775 pub fn create_nist_drbg_entropy(entropy_input: [u8; 48]) -> Box<dyn EntropySource> {
776 let mut drbg = NistAes256CtrDrbg::new();
777 drbg.randombytes_init(entropy_input);
778 Box::new(drbg)
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 #[cfg(all(not(feature = "std"), feature = "alloc"))]
785 use alloc::format;
786 #[cfg(all(test, feature = "alloc"))]
787 use alloc::vec;
788
789 use super::*;
790
791 #[test]
792 fn test_os_entropy_source_creation() {
793 let source = OsEntropySource::new();
794 assert!(!source.name().is_empty());
795 assert_eq!(source.source_type(), EntropySourceType::OperatingSystem);
796
797 let platform = source.platform();
799 assert!(!platform.is_empty());
800 assert!(source.name().contains(platform));
801
802 assert!(source.quality() > 0.0);
804 assert!(source.quality() <= 1.0);
805 }
806
807 #[test]
808 fn test_hardware_entropy_source_creation() {
809 let source = HardwareEntropySource::new();
810 assert!(!source.name().is_empty());
811 assert_eq!(source.source_type(), EntropySourceType::Hardware);
812 }
813
814 #[test]
815 #[cfg(all(feature = "std", target_arch = "x86_64"))]
816 fn test_hardware_entropy_rdrand_smoke_if_cpu_supports() {
817 if !std::arch::is_x86_feature_detected!("rdrand") {
818 return;
819 }
820 let mut source = HardwareEntropySource::new();
821 assert!(source.is_available());
822 source.initialize(&EntropyConfig::default()).unwrap();
823 let mut buf = [0u8; 64];
824 source.get_entropy(&mut buf).unwrap();
825 }
826
827 #[test]
828 fn test_deterministic_entropy_source_creation() {
829 let mut seed = [0u8; 32];
830 seed[..8].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
831 let source = DeterministicEntropySource::new(seed);
832 assert!(!source.name().is_empty());
833 assert_eq!(source.source_type(), EntropySourceType::Deterministic);
834 #[allow(clippy::float_cmp)]
835 {
836 assert_eq!(source.quality(), 0.0);
837 }
838 }
839
840 #[test]
841 fn test_deterministic_entropy_consistency() {
842 let seed = [42u8; 32];
843 let mut source1 = DeterministicEntropySource::new(seed);
844 let mut source2 = DeterministicEntropySource::new(seed);
845
846 let mut bytes1 = [0u8; 32];
847 let mut bytes2 = [0u8; 32];
848
849 source1.get_entropy(&mut bytes1).unwrap();
850 source2.get_entropy(&mut bytes2).unwrap();
851
852 assert_eq!(bytes1, bytes2);
853 }
854
855 #[test]
856 #[cfg(feature = "alloc")]
857 fn test_user_entropy_source_creation() {
858 let entropy_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
859 let source = UserEntropySource::new(entropy_data);
860 assert!(!source.name().is_empty());
861 assert_eq!(source.source_type(), EntropySourceType::User);
862 }
863
864 #[test]
865 #[cfg(feature = "alloc")]
866 fn test_user_entropy_source_with_quality() {
867 let entropy_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
868 let source = UserEntropySource::with_quality(entropy_data, 0.9);
869 #[allow(clippy::float_cmp)]
870 {
871 assert_eq!(source.quality(), 0.9);
872 }
873 }
874
875 #[test]
876 #[cfg(feature = "alloc")]
877 fn test_user_entropy_source_cycling() {
878 let entropy_data = vec![1, 2, 3];
879 let mut source = UserEntropySource::new(entropy_data);
880
881 let config = EntropyConfig {
883 max_per_call: Some(6), ..Default::default()
885 };
886 source.initialize(&config).unwrap();
887
888 let mut bytes = [0u8; 6];
889 source.get_entropy(&mut bytes).unwrap();
890
891 assert_eq!(bytes, [1, 2, 3, 1, 2, 3]);
893 }
894
895 #[test]
896 #[cfg(feature = "alloc")]
897 fn test_entropy_source_factory_deterministic() {
898 let mut seed = [0u8; 32];
899 seed[..4].copy_from_slice(&[1, 2, 3, 4]);
900 let source = EntropySourceFactory::create_deterministic_entropy(seed);
901 assert_eq!(source.source_type(), EntropySourceType::Deterministic);
902 }
903
904 #[test]
905 #[cfg(all(feature = "alloc", feature = "nist-drbg"))]
906 fn test_nist_drbg_kat_vectors() {
907 use alloc::vec::Vec;
908
909 const RNG_REF1: &str = "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA19810F5392D076276EF41277C3AB6E94A4E3B7DCC104A05BB089D338BF55C72CAB375389A94BB920BD5D6DC9E7F2EC6FDE028B6F5724BB039F3652AD98DF8CE6C97013210B84BBE81388C3D141D61957C73BCDC5E5CD92525F46A2B757B03CAB5C337004A2DA35324A325713564DAE28F57ACC6DBE32A0726190BAA6B8A0A255AA1AD01E8DD569AA36D096256C420718A69D46D8DB1C6DD40606A0BE3C235BEFE623A90593F82D6A8F9F924E44E36BE87F7D26B8445966F9EE329C426C12521E85F6FD4ECD5D566BA0A3487125D79CC64";
911 const RNG_REF2: &str = "C17E034061ED5EA817C41D61636281E816F817DCF753A91D97C018FF82FBC9B1728FC66AF114B57978FB6082B70D285140B26725AA5F7BB4409820F67E2D656EDACA30B5BB12EB5249CC3809B188CF0CC95B5AE0EFE8FC5887152CB6601B4CCF9FC411894FA0C0264EB51A481D4D7074FDF065053030C8A92BFCDD06BF18C8489C38D03784FD63001830E5A385A4A37866693F5BDAB8A8A25B519DDBF2D28268601D95BEED647E430484A227C023B0297A282F06C91376433BDE5EC3ABBA8C06B830C26452EA2FA7EDEA8DCFE20EAFCF8980B3D5AECEF89DD861ACEC1F5F7CD2AE6B3CDE3C1D80A2830DD0B9E8468AFAD161981074BEB33DF1CDFF9A5214F9F0";
912
913 fn hex_to_bytes(hex: &str) -> Vec<u8> {
915 let mut bytes = Vec::new();
916 let mut chars = hex.chars().peekable();
917 while let (Some(c1), Some(c2)) = (chars.next(), chars.next()) {
918 let byte = u8::from_str_radix(&format!("{c1}{c2}"), 16).unwrap();
919 bytes.push(byte);
920 }
921 bytes
922 }
923
924 let mut entropy_input = [0u8; 48];
925 for (i, byte) in entropy_input.iter_mut().enumerate().take(48) {
926 *byte = u8::try_from(i).expect("i < 48, so conversion to u8 is safe");
927 }
928
929 let mut drbg = NistAes256CtrDrbg::new();
930 drbg.randombytes_init(entropy_input);
931
932 let mut data = [0u8; 256];
934 drbg.get_entropy(&mut data).unwrap();
935 let ref1 = hex_to_bytes(RNG_REF1);
936 assert_eq!(data.as_slice(), ref1.as_slice());
937
938 drbg.get_entropy(&mut data).unwrap();
940 let ref2 = hex_to_bytes(RNG_REF2);
941 assert_eq!(data.as_slice(), ref2.as_slice());
942 }
943
944 #[test]
945 #[cfg(feature = "alloc")]
946 fn test_entropy_source_factory_user() {
947 let entropy_data = vec![1, 2, 3, 4, 5];
948 let source = EntropySourceFactory::create_user_entropy(entropy_data);
949 assert_eq!(source.source_type(), EntropySourceType::User);
950 }
951
952 #[test]
953 fn test_entropy_config_validation() {
954 let config = EntropyConfig {
955 min_quality: 0.9, max_per_call: Some(32),
957 ..Default::default()
958 };
959
960 let mut os_source = OsEntropySource::new();
962 assert!(os_source.initialize(&config).is_ok());
964
965 let config2 = EntropyConfig {
967 min_quality: 0.99,
968 ..config
969 };
970 let mut os_source2 = OsEntropySource::new();
971 assert!(os_source2.initialize(&config2).is_err());
973 }
974
975 #[test]
976 fn test_entropy_config_max_per_call() {
977 let config = EntropyConfig {
978 max_per_call: Some(16),
979 ..Default::default()
980 };
981
982 let mut os_source = OsEntropySource::new();
983 os_source.initialize(&config).unwrap();
984
985 let mut buffer = [0u8; 32]; assert!(os_source.get_entropy(&mut buffer).is_err());
988 }
989
990 #[test]
991 #[cfg(feature = "alloc")]
992 fn test_entropy_source_factory_with_config() {
993 let config = EntropyConfig {
994 min_quality: 0.8,
995 max_per_call: Some(64),
996 ..Default::default()
997 };
998
999 let result = EntropySourceFactory::create_best_available_with_config(&config);
1001 let _ = result;
1003 }
1004
1005 #[test]
1006 #[cfg(any(feature = "std", feature = "alloc"))]
1007 fn test_platform_specific_error_messages() {
1008 let mut os_source = OsEntropySource::new();
1009 let platform = os_source.platform();
1010
1011 let config = EntropyConfig {
1013 min_quality: 1.0, ..Default::default()
1015 };
1016
1017 let result = os_source.initialize(&config);
1018 assert!(result.is_err());
1019 if let Err(error) = result {
1020 let error_msg = format!("{error}");
1021 assert!(error_msg.contains("quality below required minimum"));
1022 }
1023
1024 let config2 = EntropyConfig {
1026 max_per_call: Some(1000), ..Default::default()
1028 };
1029
1030 let result2 = os_source.initialize(&config2);
1031 assert!(result2.is_err());
1032 if let Err(error) = result2 {
1033 let error_msg = format!("{error}");
1034 assert!(error_msg.contains("exceeds OS entropy source limit"));
1035 }
1036
1037 assert!(!platform.is_empty());
1039 assert!(os_source.name().contains(platform));
1040 }
1041}