diff --git a/keyfork/src/cli/shard.rs b/keyfork/src/cli/shard.rs index 945f10b..1610bd4 100644 --- a/keyfork/src/cli/shard.rs +++ b/keyfork/src/cli/shard.rs @@ -1,10 +1,12 @@ use super::Keyfork; use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum}; use std::{ - io::{stdin, stdout, BufRead, BufReader, Read, Write}, + io::{stdin, stdout, Read, Write}, path::{Path, PathBuf}, }; +const COULD_NOT_DETERMINE_FORMAT: &str = "could not determine format, try including --format"; + #[derive(Debug, Clone)] enum Format { OpenPGP(OpenPGP), @@ -42,6 +44,14 @@ trait ShardExec { ) -> Result<(), Box> where T: AsRef; + + fn decrypt( + &self, + key_discovery: Option, + input: impl Read + Send + Sync, + ) -> Result<(), Box> + where + T: AsRef; } #[derive(Clone, Debug)] @@ -99,6 +109,32 @@ impl ShardExec for OpenPGP { Ok(()) } + + fn decrypt( + &self, + key_discovery: Option, + input: impl Read + Send + Sync, + ) -> Result<(), Box> + where + T: AsRef, + { + 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::decrypt( + &certs, + &encrypted_metadata, + encrypted_messages.make_contiguous(), + )?; + Ok(()) + } } #[derive(Clone, Debug)] @@ -125,6 +161,13 @@ pub enum ShardSubcommands { key_discovery: PathBuf, }, + /// Decrypt a single share and re-encrypt it to an ephemeral symmetric key using mnemonic-based + /// prompts. The mnemonics can be sent over insecure channels. + Transport { + /// The path to discover private keys from. + key_discovery: Option, + }, + /// Combine multiple encrypted shares into a hex-encoded secret, printed to stdout. /// /// This command only accepts input from `keyfork shard split`, and is dependent on the format @@ -143,8 +186,15 @@ impl ShardSubcommands { shard: &Shard, _keyfork: &Keyfork, ) -> Result<(), Box> { - let stdin = stdin(); + let mut stdin = stdin(); let mut stdout = stdout(); + let mut input = String::new(); + stdin.read_to_string(&mut input)?; + let mut format = shard.format.clone(); + // bang sandwich macro fun + if input.contains("BEGIN PGP MESSAGE") && !matches!(self, ShardSubcommands::Split { .. }) { + let _ = format.insert(Format::OpenPGP(OpenPGP)); + } match self { ShardSubcommands::Split { threshold, @@ -152,30 +202,30 @@ impl ShardSubcommands { 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 { + let secret = smex::decode(input.trim())?; + match 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"), + None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), } } - ShardSubcommands::Combine { - key_discovery, - } => match &shard.format { + ShardSubcommands::Transport { key_discovery } => match format { + Some(Format::OpenPGP(o)) => o.decrypt(key_discovery.as_ref(), input.as_bytes()), + Some(Format::P256(_p)) => todo!(), + None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), + }, + ShardSubcommands::Combine { key_discovery } => match format { Some(Format::OpenPGP(o)) => { - o.combine(key_discovery.as_ref(), stdin, &mut stdout) + o.combine(key_discovery.as_ref(), input.as_bytes(), &mut stdout) } Some(Format::P256(_p)) => { todo!() } - None => panic!("--format was not given"), + None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), }, } }