keyfork: add `keyfork shard`
This commit is contained in:
parent
a72bfaecec
commit
7da9738d52
|
@ -882,6 +882,7 @@ dependencies = [
|
|||
"clap",
|
||||
"keyfork-mnemonic-util",
|
||||
"keyfork-plumbing",
|
||||
"keyfork-shard",
|
||||
"smex",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
|
@ -11,3 +11,4 @@ clap = { version = "4.4.2", features = ["derive", "env"] }
|
|||
thiserror = "1.0.48"
|
||||
smex = { version = "0.1.0", path = "../smex" }
|
||||
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};
|
||||
|
||||
mod mnemonic;
|
||||
mod shard;
|
||||
|
||||
/// The Kitchen Sink of Entropy.
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
|
@ -16,6 +17,9 @@ pub enum KeyforkCommands {
|
|||
/// Mnemonic generation and persistence utilities.
|
||||
Mnemonic(mnemonic::Mnemonic),
|
||||
|
||||
/// Secret sharing utilities.
|
||||
Shard(shard::Shard),
|
||||
|
||||
/// Keyforkd background daemon to manage seed creation.
|
||||
Daemon,
|
||||
}
|
||||
|
@ -27,6 +31,10 @@ impl KeyforkCommands {
|
|||
let response = m.command.handle(m, keyfork)?;
|
||||
println!("{response}");
|
||||
}
|
||||
KeyforkCommands::Shard(s) => {
|
||||
// TODO: When actually fleshing out, this takes a `Read` and a `Write`
|
||||
s.command.handle(s, keyfork)?;
|
||||
}
|
||||
KeyforkCommands::Daemon => {
|
||||
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