keyfork-mnemonic-generate: initial commit

This commit is contained in:
Ryan Heywood 2023-08-16 05:43:40 -05:00
parent 6e4f63e248
commit 298f9a1e26
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
8 changed files with 2308 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

93
Cargo.lock generated Normal file
View File

@ -0,0 +1,93 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "keyfork-mnemonic-generate"
version = "0.1.0"
dependencies = [
"sha2",
]
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "sha2"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

5
Cargo.toml Normal file
View File

@ -0,0 +1,5 @@
[workspace]
members = [
"keyfork-mnemonic-generate"
]

View File

@ -4,6 +4,10 @@ An opinionated and modular toolchain for generating and managing a wide range
of cryptographic keys offline and on smartcards from a shared bip39 mnemonic
phrase.
This toolchain uses entropy generated from an agent controlling a bip32 seed
to generate keypairs, ensuring the keys are deterministic and contextually
unique.
Note: The following document is all proposed, and not yet implemented.
## Features

7
keyfork-mnemonic-generate/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "keyfork-mnemonic-generate"
version = "0.1.0"

View File

@ -0,0 +1,9 @@
[package]
name = "keyfork-mnemonic-generate"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sha2 = "0.10.7"

View File

@ -0,0 +1,140 @@
use std::{
env,
error::Error,
fs::{read_dir, read_to_string, File},
io::Read,
};
use sha2::{Digest, Sha256};
/// Usage: keyfork-mnemonic-generate
/// 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 = sha2::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(())
}

File diff suppressed because it is too large Load Diff