diff --git a/README.md b/README.md index 7ccdf25..ff33e88 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ On an airgapped system, run the following command to generate a file containing encrypted shards of a generated seed: ```sh -keyfork wizard generate-shard-secret --threshold $N --max $M --keys-per-shard $I > shards.pgp +keyfork wizard generate-shard-secret --threshold $N --max $M --keys-per-shard $I --output shards.pgp ``` Once generated, the shards file can be safely stored in any location, as the diff --git a/keyfork-user-guide/src/bin/keyfork/wizard/index.md b/keyfork-user-guide/src/bin/keyfork/wizard/index.md index 178e3f5..7596a12 100644 --- a/keyfork-user-guide/src/bin/keyfork/wizard/index.md +++ b/keyfork-user-guide/src/bin/keyfork/wizard/index.md @@ -20,6 +20,7 @@ It is recommended to use smart cards dedicated to the purpose of seed recovery. secret. * `max`: The maximum amount of shardholders. * `keys-per-shard`: The amount of smart cardsz to provision per-shardholder. +* `output`: The file to write the generated shard to. ### Prompts diff --git a/keyfork/src/cli/wizard.rs b/keyfork/src/cli/wizard.rs index eefe02d..a60e221 100644 --- a/keyfork/src/cli/wizard.rs +++ b/keyfork/src/cli/wizard.rs @@ -1,6 +1,6 @@ use super::Keyfork; use clap::{Parser, Subcommand}; -use std::{collections::HashSet, io::IsTerminal}; +use std::{collections::HashSet, io::IsTerminal, path::PathBuf, fs::OpenOptions}; use card_backend_pcsc::PcscBackend; use openpgp_card_sequoia::{state::Open, types::KeyType, Card}; @@ -12,7 +12,7 @@ use keyfork_derive_util::{ }; use keyfork_prompt::{ validators::{PinValidator, Validator}, - Message, Terminal, PromptHandler, + Message, PromptHandler, Terminal, }; #[derive(thiserror::Error, Debug)] @@ -100,16 +100,18 @@ fn factory_reset_current_card( Ok(()) } -fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8) -> Result<()> { +fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8, output_file: &Option) -> Result<()> { let seed = keyfork_entropy::generate_entropy_of_size(256 / 8)?; let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?; let mut certs = vec![]; let mut seen_cards: HashSet = HashSet::new(); let stdout = std::io::stdout(); - assert!( - !stdout.is_terminal(), - "not printing shard to terminal, redirect output" - ); + if output_file.is_none() { + assert!( + !stdout.is_terminal(), + "not printing shard to terminal, redirect output" + ); + } let user_pin_validator = PinValidator { min_length: Some(6), @@ -145,7 +147,12 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8) -> Result<( certs.push(cert); } - keyfork_shard::openpgp::split(threshold, certs, &seed, std::io::stdout())?; + if let Some(output_file) = output_file { + let output = OpenOptions::new().write(true).open(output_file)?; + keyfork_shard::openpgp::split(threshold, certs, &seed, output)?; + } else { + keyfork_shard::openpgp::split(threshold, certs, &seed, std::io::stdout())?; + } Ok(()) } @@ -168,6 +175,10 @@ pub enum WizardSubcommands { /// The amount of smart cards to provision per-shard. #[arg(long, default_value = "1")] keys_per_shard: u8, + + /// The file to write the generated shard file to. + #[arg(long)] + output: Option, }, } @@ -178,7 +189,8 @@ impl WizardSubcommands { threshold, max, keys_per_shard, - } => generate_shard_secret(*threshold, *max, *keys_per_shard), + output, + } => generate_shard_secret(*threshold, *max, *keys_per_shard, output), } } }