keyfork: add `keyfork shard`
This commit is contained in:
parent
a72bfaecec
commit
7da9738d52
|
@ -882,6 +882,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"keyfork-mnemonic-util",
|
"keyfork-mnemonic-util",
|
||||||
"keyfork-plumbing",
|
"keyfork-plumbing",
|
||||||
|
"keyfork-shard",
|
||||||
"smex",
|
"smex",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
|
@ -11,3 +11,4 @@ clap = { version = "4.4.2", features = ["derive", "env"] }
|
||||||
thiserror = "1.0.48"
|
thiserror = "1.0.48"
|
||||||
smex = { version = "0.1.0", path = "../smex" }
|
smex = { version = "0.1.0", path = "../smex" }
|
||||||
keyfork-plumbing = { version = "0.1.0", path = "../keyfork-plumbing" }
|
keyfork-plumbing = { version = "0.1.0", path = "../keyfork-plumbing" }
|
||||||
|
keyfork-shard = { version = "0.1.0", path = "../keyfork-shard" }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
mod mnemonic;
|
mod mnemonic;
|
||||||
|
mod shard;
|
||||||
|
|
||||||
/// The Kitchen Sink of Entropy.
|
/// The Kitchen Sink of Entropy.
|
||||||
#[derive(Parser, Clone, Debug)]
|
#[derive(Parser, Clone, Debug)]
|
||||||
|
@ -16,6 +17,9 @@ pub enum KeyforkCommands {
|
||||||
/// Mnemonic generation and persistence utilities.
|
/// Mnemonic generation and persistence utilities.
|
||||||
Mnemonic(mnemonic::Mnemonic),
|
Mnemonic(mnemonic::Mnemonic),
|
||||||
|
|
||||||
|
/// Secret sharing utilities.
|
||||||
|
Shard(shard::Shard),
|
||||||
|
|
||||||
/// Keyforkd background daemon to manage seed creation.
|
/// Keyforkd background daemon to manage seed creation.
|
||||||
Daemon,
|
Daemon,
|
||||||
}
|
}
|
||||||
|
@ -27,6 +31,10 @@ impl KeyforkCommands {
|
||||||
let response = m.command.handle(m, keyfork)?;
|
let response = m.command.handle(m, keyfork)?;
|
||||||
println!("{response}");
|
println!("{response}");
|
||||||
}
|
}
|
||||||
|
KeyforkCommands::Shard(s) => {
|
||||||
|
// TODO: When actually fleshing out, this takes a `Read` and a `Write`
|
||||||
|
s.command.handle(s, keyfork)?;
|
||||||
|
}
|
||||||
KeyforkCommands::Daemon => {
|
KeyforkCommands::Daemon => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
use super::Keyfork;
|
||||||
|
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
|
||||||
|
use std::{
|
||||||
|
io::{stdin, stdout, BufRead, BufReader, Read, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Format {
|
||||||
|
OpenPGP(OpenPGP),
|
||||||
|
P256(P256),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueEnum for Format {
|
||||||
|
fn value_variants<'a>() -> &'a [Self] {
|
||||||
|
&[Self::OpenPGP(OpenPGP), Self::P256(P256)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||||
|
Some(match self {
|
||||||
|
Format::OpenPGP(_) => PossibleValue::new("openpgp"),
|
||||||
|
Format::P256(_) => PossibleValue::new("p256"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ShardExec {
|
||||||
|
fn split(
|
||||||
|
&self,
|
||||||
|
threshold: u8,
|
||||||
|
max: u8,
|
||||||
|
key_discovery: impl AsRef<Path>,
|
||||||
|
secret: &[u8],
|
||||||
|
output: &mut impl Write,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
fn combine<T>(
|
||||||
|
&self,
|
||||||
|
threshold: u8,
|
||||||
|
key_discovery: Option<T>,
|
||||||
|
input: impl Read + Send + Sync,
|
||||||
|
output: &mut impl Write,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct OpenPGP;
|
||||||
|
|
||||||
|
impl ShardExec for OpenPGP {
|
||||||
|
fn split(
|
||||||
|
&self,
|
||||||
|
threshold: u8,
|
||||||
|
max: u8,
|
||||||
|
key_discovery: impl AsRef<Path>,
|
||||||
|
secret: &[u8],
|
||||||
|
output: &mut impl Write,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Get certs and input
|
||||||
|
let certs = keyfork_shard::openpgp::discover_certs(key_discovery.as_ref())?;
|
||||||
|
assert_eq!(
|
||||||
|
certs.len(),
|
||||||
|
max.into(),
|
||||||
|
"cert count {} != max {max}",
|
||||||
|
certs.len()
|
||||||
|
);
|
||||||
|
keyfork_shard::openpgp::split(threshold, certs, secret, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine<T>(
|
||||||
|
&self,
|
||||||
|
threshold: u8,
|
||||||
|
key_discovery: Option<T>,
|
||||||
|
input: impl Read + Send + Sync,
|
||||||
|
output: &mut impl Write,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let certs = key_discovery
|
||||||
|
.map(|kd| keyfork_shard::openpgp::discover_certs(kd.as_ref()))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(vec![]);
|
||||||
|
|
||||||
|
let mut encrypted_messages = keyfork_shard::openpgp::parse_messages(input)?;
|
||||||
|
let encrypted_metadata = encrypted_messages
|
||||||
|
.pop_front()
|
||||||
|
.expect("any pgp encrypted message");
|
||||||
|
|
||||||
|
keyfork_shard::openpgp::combine(
|
||||||
|
threshold,
|
||||||
|
certs,
|
||||||
|
encrypted_metadata,
|
||||||
|
encrypted_messages.into(),
|
||||||
|
output,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct P256;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
|
pub enum ShardSubcommands {
|
||||||
|
/// Split a secret into multiple shares, using Shamir's Secret Sharing.
|
||||||
|
Split {
|
||||||
|
/// The amount of shares required to recombine a secret.
|
||||||
|
#[arg(long)]
|
||||||
|
threshold: u8,
|
||||||
|
|
||||||
|
/// The total amount of shares to generate.
|
||||||
|
#[arg(long)]
|
||||||
|
max: u8,
|
||||||
|
|
||||||
|
/// The path to discover public keys from.
|
||||||
|
key_discovery: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Combine multiple shares into a secret
|
||||||
|
Combine {
|
||||||
|
/// The amount of sharesr equired to recombine a secret.
|
||||||
|
#[arg(long)]
|
||||||
|
threshold: u8,
|
||||||
|
|
||||||
|
/// The path to discover private keys from.
|
||||||
|
key_discovery: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShardSubcommands {
|
||||||
|
pub fn handle(
|
||||||
|
&self,
|
||||||
|
shard: &Shard,
|
||||||
|
_keyfork: &Keyfork,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let stdin = stdin();
|
||||||
|
let mut stdout = stdout();
|
||||||
|
match self {
|
||||||
|
ShardSubcommands::Split {
|
||||||
|
threshold,
|
||||||
|
max,
|
||||||
|
key_discovery,
|
||||||
|
} => {
|
||||||
|
assert!(threshold <= max, "threshold {threshold} <= max {max}");
|
||||||
|
let mut input = BufReader::new(stdin);
|
||||||
|
let mut hex_line = String::new();
|
||||||
|
input.read_line(&mut hex_line)?;
|
||||||
|
let secret = smex::decode(hex_line.trim())?;
|
||||||
|
match &shard.format {
|
||||||
|
Some(Format::OpenPGP(o)) => {
|
||||||
|
o.split(*threshold, *max, key_discovery, &secret, &mut stdout)
|
||||||
|
}
|
||||||
|
Some(Format::P256(_p)) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
None => panic!("--format was not given"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShardSubcommands::Combine {
|
||||||
|
threshold,
|
||||||
|
key_discovery,
|
||||||
|
} => match &shard.format {
|
||||||
|
Some(Format::OpenPGP(o)) => {
|
||||||
|
o.combine(*threshold, key_discovery.as_ref(), stdin, &mut stdout)
|
||||||
|
}
|
||||||
|
Some(Format::P256(_p)) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
None => panic!("--format was not given"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug, Clone)]
|
||||||
|
pub struct Shard {
|
||||||
|
/// Which format to use for encoding/encrypting and decoding/decrypting shares.
|
||||||
|
#[arg(long, value_enum, global = true)]
|
||||||
|
format: Option<Format>,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: ShardSubcommands,
|
||||||
|
}
|
Loading…
Reference in New Issue