//! Utilities for reading entropy from secure sources. use keyfork_bug::bug; use std::{ fs::{read_dir, read_to_string, File}, io::Read, }; static WARNING_LINKS: [&str; 1] = ["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"]; fn ensure_safe_kernel_version() { let kernel_version = read_to_string("/proc/version").expect(bug!("Unable to open file: /proc/version")); let v = kernel_version .split(' ') .nth(2) .expect(bug!("Unable to parse kernel version")) .split('.') .take(2) .map(str::parse) .map(|x| x.expect(bug!("Unable to parse kernel version number"))) .collect::>(); let [major, minor, ..] = v.as_slice() else { panic!("Unable to determine major and minor: {kernel_version}"); }; assert!( [major, minor] > [&5, &4], "kernel can't generate clean entropy: {}", WARNING_LINKS[0] ); } fn ensure_offline() { let paths = read_dir("/sys/class/net").expect(bug!("Unable to read network interfaces")); for entry in paths { let mut path = entry.expect(bug!("Unable to read directory entry")).path(); if path .as_os_str() .to_str() .expect(bug!("Unable to decode UTF-8 filepath")) .split('/') .last() .expect(bug!("No data in file path")) == "lo" { continue; } path.push("operstate"); let isup = read_to_string(&path).expect(bug!("Unable to read operstate of network interfaces")); assert_ne!(isup.trim(), "up", "No network interfaces should be up"); } } /// Ensure the system is safe. /// /// # Examples /// ```rust /// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1"); /// keyfork_entropy::ensure_safe(); /// ``` /// /// When running on a system that's online, or running an outdated kernel: /// /// ```rust,should_panic /// # // NOTE: sometimes, the environment variable is set, for testing purposes. I'm not sure how /// # // to un-set it. Set it to a sentinel value. /// # std::env::set_var("SHOOT_SELF_IN_FOOT", "test-must-fail"); /// # std::env::set_var("INSECURE_HARDWARE_ALLOWED", "test-must-fail"); /// keyfork_entropy::ensure_safe(); /// ``` pub fn ensure_safe() { if !std::env::vars().any(|(name, value)| { (name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED") && value != "test-must-fail" }) { ensure_safe_kernel_version(); ensure_offline(); } } /// Read system entropy of a given size. /// /// # Errors /// An error may be returned if an error occurred while reading from the random source. /// /// # Examples /// ```rust,no_run /// # fn main() -> Result<(), Box> { /// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1"); /// let entropy = keyfork_entropy::generate_entropy_of_size(64)?; /// assert_eq!(entropy.len(), 64); /// # Ok(()) /// # } /// ``` pub fn generate_entropy_of_size(byte_count: usize) -> Result, std::io::Error> { ensure_safe(); let mut vec = vec![0u8; byte_count]; let mut entropy_file = File::open("/dev/urandom")?; entropy_file.read_exact(&mut vec[..])?; Ok(vec) } /// Read system entropy of a constant size. /// /// # Errors /// An error may be returned if an error occurred while reading from the random source. /// /// # Examples /// ```rust,no_run /// # fn main() -> Result<(), Box> { /// # std::env::set_var("SHOOT_SELF_IN_FOOT", "1"); /// let entropy = keyfork_entropy::generate_entropy_of_const_size::<64>()?; /// assert_eq!(entropy.len(), 64); /// # Ok(()) /// # } /// ``` pub fn generate_entropy_of_const_size() -> Result<[u8; N], std::io::Error> { let mut output = [0u8; N]; let mut entropy_file = File::open("/dev/urandom")?; entropy_file.read_exact(&mut output[..])?; Ok(output) }