keyfork-mnemonic-generate: initial commit
This commit is contained in:
parent
6e4f63e248
commit
ad32c927d2
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -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"
|
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"keyfork-mnemonic-generate"
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
Loading…
Reference in New Issue