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
|
of cryptographic keys offline and on smartcards from a shared bip39 mnemonic
|
||||||
phrase.
|
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.
|
Note: The following document is all proposed, and not yet implemented.
|
||||||
|
|
||||||
## Features
|
## 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