diff --git a/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs b/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs index abfd00c..060f843 100644 --- a/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs +++ b/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs @@ -1,7 +1,7 @@ use std::{ env, - io::stdin, - path::PathBuf, + fs::File, + path::{Path, PathBuf}, process::ExitCode, }; @@ -9,10 +9,11 @@ use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert, parse_messa type Result> = std::result::Result; -fn validate<'a>( - key_discovery: impl Into>, -) -> Result> { - let key_discovery = key_discovery.into().map(PathBuf::from); +fn validate( + shard: impl AsRef, + key_discovery: Option<&str>, +) -> Result<(File, Vec)> { + let key_discovery = key_discovery.map(PathBuf::from); key_discovery.as_ref().map(std::fs::metadata).transpose()?; // Load certs from path @@ -21,20 +22,20 @@ fn validate<'a>( .transpose()? .unwrap_or(vec![]); - Ok(certs) + Ok((File::open(shard)?, certs)) } fn run() -> Result<()> { let mut args = env::args(); let program_name = args.next().expect("program name"); let args = args.collect::>(); - let cert_list = match args.as_slice() { - [key_discovery] => validate(key_discovery.as_str())?, - [] => validate(None)?, - _ => panic!("Usage: {program_name} threshold [key_discovery]"), + let (messages_file, cert_list) = match args.as_slice() { + [shard, key_discovery] => validate(shard, Some(key_discovery))?, + [shard] => validate(shard, None)?, + _ => panic!("Usage: {program_name} [key_discovery]"), }; - let mut encrypted_messages = parse_messages(stdin())?; + let mut encrypted_messages = parse_messages(messages_file)?; let encrypted_metadata = encrypted_messages .pop_front() diff --git a/keyfork-user-guide/src/bin/keyfork-shard/openpgp/combine.md b/keyfork-user-guide/src/bin/keyfork-shard/openpgp/combine.md index e728221..d886cf9 100644 --- a/keyfork-user-guide/src/bin/keyfork-shard/openpgp/combine.md +++ b/keyfork-user-guide/src/bin/keyfork-shard/openpgp/combine.md @@ -4,8 +4,9 @@ Combine `threshold` shares into a previously [`split`] secret. ## Arguments -`keyfork-shard-combine-openpgp [key_discovery]` +`keyfork-shard-combine-openpgp [key_discovery]` +* `shard`: The shard file to read from. * `key_discovery`: A file or directory containing OpenPGP keys. If the number of keys found is less than `threshold`, an OpenPGP Card fallback will be used to decrypt the rest of the messages. @@ -17,10 +18,6 @@ The terminal may be overridden if the default pinentry command is used if an OpenPGP key file has an encrypted secret key or to prompt for the PIN for an OpenPGP smart card. -## Input - -OpenPGP messages from [`split`]. - ## Output Hex-encoded secret. @@ -29,10 +26,10 @@ Hex-encoded secret. ```sh # Decrypt using only smartcards -keyfork-shard-combine-openpgp < shard.pgp +keyfork-shard-combine-openpgp shard.pgp # Decrypt using on-disk private keys -keyfork-shard-combine-openpgp key_discovery.pgp < shard.pgp +keyfork-shard-combine-openpgp key_discovery.pgp shard.pgp ``` [`split`]: ./split.md diff --git a/keyfork-user-guide/src/bin/keyfork/shard/index.md b/keyfork-user-guide/src/bin/keyfork/shard/index.md index 72b1a49..9b548e1 100644 --- a/keyfork-user-guide/src/bin/keyfork/shard/index.md +++ b/keyfork-user-guide/src/bin/keyfork/shard/index.md @@ -68,8 +68,9 @@ Combine `threshold` shares into a secret. ### Arguments -`keyfork shard combine [key_discovery]` +`keyfork shard combine [key_discovery]` +* `shard`: A file containing the encrypted shards. * `key_discovery`: Either a file or a directory containing public keys. If a file, load all private keys from a file. If a directory, for every file in the directory (non-recursively), load @@ -77,11 +78,6 @@ Combine `threshold` shares into a secret. If the amount of keys found is less than `threshold`, it is up to the format to determine how to discover the keys. -### Input - -The input of the command is dependent on the format, but should be the exact -same as the output from the `split` command previously used. - ### Output Hex-encoded secret. @@ -106,8 +102,9 @@ by a remote recovery operator. ### Arguments -`keyfork shard transport [key_discovery]` +`keyfork shard transport [key_discovery]` +* `shard`: A file containing encrypted shards. * `key_discovery`: Either a file or a directory containing public keys. If a file, load all private keys from a file. If a directory, for every file in the directory (non-recursively), load @@ -115,11 +112,6 @@ by a remote recovery operator. If the amount of keys found is less than `threshold`, it is up to the format to determine how to discover the keys. -### Input - -The input of the command is dependent on the format, but should be the exact -same as the output from the `split` command previously used. - ### Prompts The command will prompt for 33 words from the remote shard recovery operator, @@ -131,10 +123,10 @@ operator. ```sh # Transport using a smart card -keyfork shard transport < shard.pgp +keyfork shard transport shard.pgp # Transport using on-disk private keys -keyfork shard transport key_discovery.pgp < shard.pgp +keyfork shard transport key_discovery.pgp shard.pgp ``` [`keyfork recover remote-shard`]: ../recover/index.md#keyfork-recover-remote-shard diff --git a/keyfork/src/cli/shard.rs b/keyfork/src/cli/shard.rs index 1610bd4..6439b5f 100644 --- a/keyfork/src/cli/shard.rs +++ b/keyfork/src/cli/shard.rs @@ -164,6 +164,9 @@ pub enum ShardSubcommands { /// 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 load the shard from. + shard: PathBuf, + /// The path to discover private keys from. key_discovery: Option, }, @@ -175,6 +178,9 @@ pub enum ShardSubcommands { /// hardware metadata discovery, any hardware key used to split may be used to decrypt metadata /// used to combine. Combine { + /// The path to load the shards from. + shard: PathBuf, + /// The path to discover private keys from. key_discovery: Option, }, @@ -186,21 +192,16 @@ impl ShardSubcommands { shard: &Shard, _keyfork: &Keyfork, ) -> Result<(), Box> { - let mut stdin = stdin(); + let 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, max, key_discovery, } => { + let input = std::io::read_to_string(stdin)?; assert!(threshold <= max, "threshold {threshold} <= max {max}"); let secret = smex::decode(input.trim())?; match format { @@ -213,20 +214,44 @@ impl ShardSubcommands { None => panic!("{COULD_NOT_DETERMINE_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(), input.as_bytes(), &mut stdout) + ShardSubcommands::Transport { + shard, + key_discovery, + } => { + let shard_content = std::fs::read_to_string(shard)?; + if shard_content.contains("BEGIN PGP MESSAGE") { + let _ = format.insert(Format::OpenPGP(OpenPGP)); } - Some(Format::P256(_p)) => { - todo!() + + match format { + Some(Format::OpenPGP(o)) => { + o.decrypt(key_discovery.as_ref(), shard_content.as_bytes()) + } + Some(Format::P256(_p)) => todo!(), + None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), } - None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), - }, + } + ShardSubcommands::Combine { + shard, + key_discovery, + } => { + let shard_content = std::fs::read_to_string(shard)?; + if shard_content.contains("BEGIN PGP MESSAGE") { + let _ = format.insert(Format::OpenPGP(OpenPGP)); + } + + match format { + Some(Format::OpenPGP(o)) => o.combine( + key_discovery.as_ref(), + shard_content.as_bytes(), + &mut stdout, + ), + Some(Format::P256(_p)) => { + todo!() + } + None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), + } + } } } }