141 lines
4.1 KiB
Rust
141 lines
4.1 KiB
Rust
use std::{
|
|
env,
|
|
error::Error,
|
|
fs::{read_dir, read_to_string, File},
|
|
io::Read,
|
|
};
|
|
|
|
use sha2::{Digest, Sha256};
|
|
|
|
/// Usage: keyfork-mnemonic-generate [bitsize: 128 or 256]
|
|
/// CHECKS:
|
|
/// * If the system is online
|
|
/// * If a kernel is running post-BLAKE2
|
|
///
|
|
/// TODO:
|
|
/// * --features kitchen-sink: load username; system time's most random, precise bits; hostname;
|
|
/// kernel version; other env specific shit into a CSPRNG
|
|
|
|
static WARNING_LINKS: [&str; 1] =
|
|
["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"];
|
|
|
|
fn check_kernel_version() {
|
|
let kernel_version = read_to_string("/proc/version").expect("/proc/version");
|
|
let v = kernel_version
|
|
.split(' ')
|
|
.nth(2)
|
|
.expect("Unable to parse kernel version")
|
|
.split('.')
|
|
.take(2)
|
|
.map(str::parse)
|
|
.map(|x| x.expect("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 check_online() {
|
|
let paths = read_dir("/sys/class/net").expect("Unable to read network interfaces");
|
|
for entry in paths {
|
|
let mut path = entry.expect("Unable to read directory entry").path();
|
|
if path
|
|
.as_os_str()
|
|
.to_str()
|
|
.expect("Unable to decode UTF-8 filepath")
|
|
.split('/')
|
|
.last()
|
|
.unwrap()
|
|
== "lo"
|
|
{
|
|
continue;
|
|
}
|
|
path.push("operstate");
|
|
let isup = read_to_string(&path).expect("Unable to read operstate of network interfaces");
|
|
assert_ne!(isup.trim(), "up", "No network interfaces should be up");
|
|
}
|
|
}
|
|
|
|
fn build_wordlist() -> Vec<String> {
|
|
let wordlist_file = include_str!("wordlist.txt");
|
|
wordlist_file
|
|
.lines()
|
|
.skip(1)
|
|
.map(|x| x.trim().to_string())
|
|
.collect()
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
if !env::vars()
|
|
.any(|(name, _)| name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED")
|
|
{
|
|
check_kernel_version();
|
|
check_online();
|
|
}
|
|
|
|
let wordlist = build_wordlist();
|
|
assert_eq!(
|
|
wordlist.len(),
|
|
2usize.pow(11),
|
|
"Wordlist did not include correct word count"
|
|
);
|
|
|
|
let bit_size: usize = env::args()
|
|
.nth(1)
|
|
.unwrap_or(String::from("256"))
|
|
.parse()
|
|
.expect("Expected integer bit size");
|
|
assert!(
|
|
bit_size == 128 || bit_size == 256,
|
|
"Only 12 or 24 word mnemonics are supported"
|
|
);
|
|
|
|
let mut random_handle = File::open("/dev/urandom")?;
|
|
let entropy = &mut [0u8; 256 / 8];
|
|
random_handle.read_exact(&mut entropy[..])?;
|
|
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(&entropy);
|
|
let hash = hasher.finalize();
|
|
let checksum = &hash[..bit_size / 32 / 8];
|
|
|
|
let seed = [&entropy[..bit_size / 8], checksum].concat();
|
|
let seed_bits = seed
|
|
.iter()
|
|
.flat_map(|byte| {
|
|
[
|
|
byte & (1 << 0) != 0,
|
|
byte & (1 << 1) != 0,
|
|
byte & (1 << 2) != 0,
|
|
byte & (1 << 3) != 0,
|
|
byte & (1 << 4) != 0,
|
|
byte & (1 << 5) != 0,
|
|
byte & (1 << 6) != 0,
|
|
byte & (1 << 7) != 0,
|
|
]
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let words = seed_bits.chunks_exact(11).map(|chunk| {
|
|
let index = usize::from(chunk[0])
|
|
+ (usize::from(chunk[1]) << 1)
|
|
+ (usize::from(chunk[2]) << 2)
|
|
+ (usize::from(chunk[3]) << 3)
|
|
+ (usize::from(chunk[4]) << 4)
|
|
+ (usize::from(chunk[5]) << 5)
|
|
+ (usize::from(chunk[6]) << 6)
|
|
+ (usize::from(chunk[7]) << 7)
|
|
+ (usize::from(chunk[8]) << 8)
|
|
+ (usize::from(chunk[9]) << 9)
|
|
+ (usize::from(chunk[10]) << 10);
|
|
wordlist[index].clone()
|
|
}).collect::<Vec<_>>();
|
|
println!("{}", words.join(" "));
|
|
|
|
Ok(())
|
|
}
|