keyfork/crates/util/keyfork-entropy/src/lib.rs

126 lines
3.9 KiB
Rust
Raw Normal View History

2024-01-16 02:44:48 +00:00
//! 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::<Vec<u32>>();
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");
}
}
2024-01-16 02:44:48 +00:00
/// Ensure the system is safe.
2024-02-10 06:40:02 +00:00
///
/// # 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();
}
}
2024-01-16 02:44:48 +00:00
/// Read system entropy of a given size.
///
/// # Errors
/// An error may be returned if an error occurred while reading from the random source.
2024-02-10 06:40:02 +00:00
///
/// # Examples
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # 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<Vec<u8>, 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<dyn std::error::Error>> {
/// # 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<const N: usize>() -> 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)
}