From d5c3587343a5d9a1249e86e699748167c0ab889f Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 29 Jul 2024 00:48:10 -0400 Subject: [PATCH] keyfork: add `bottoms-up` wizard --- .../derive/keyfork-derive-openpgp/src/main.rs | 2 +- crates/keyfork/src/cli/wizard.rs | 59 ++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/crates/derive/keyfork-derive-openpgp/src/main.rs b/crates/derive/keyfork-derive-openpgp/src/main.rs index 8431b17..c803ad4 100644 --- a/crates/derive/keyfork-derive-openpgp/src/main.rs +++ b/crates/derive/keyfork-derive-openpgp/src/main.rs @@ -83,7 +83,7 @@ fn validate( let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; let path = DerivationPath::from_str(path)?; - assert_eq!(2, path.len(), "Expected path of m/{index}/account_id'"); + assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'"); let given_index = path.iter().next().expect("checked .len() above"); assert_eq!( diff --git a/crates/keyfork/src/cli/wizard.rs b/crates/keyfork/src/cli/wizard.rs index 1dcdf19..227a4f8 100644 --- a/crates/keyfork/src/cli/wizard.rs +++ b/crates/keyfork/src/cli/wizard.rs @@ -1,6 +1,11 @@ use super::Keyfork; use clap::{Parser, Subcommand}; -use std::{collections::HashSet, fs::File, io::IsTerminal, path::PathBuf}; +use std::{ + collections::HashSet, + fs::File, + io::IsTerminal, + path::{Path, PathBuf}, +}; use card_backend_pcsc::PcscBackend; use openpgp_card_sequoia::{state::Open, types::KeyType, Card}; @@ -185,6 +190,35 @@ fn generate_shard_secret( Ok(()) } +fn bottoms_up(key_discovery: &Path, threshold: u8, output: &Option) -> Result<()> { + let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?; + let stdout = std::io::stdout(); + if output.is_none() { + assert!( + !stdout.is_terminal(), + "not printing shard to terminal, redirect output" + ); + } + + let opgp = OpenPGP::::new(); + let certs = OpenPGP::::discover_certs(key_discovery)?; + + if let Some(output_file) = output { + let output = File::create(output_file)?; + opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], output)?; + } else { + opgp.shard_and_encrypt( + threshold, + certs.len() as u8, + &seed, + &certs[..], + std::io::stdout(), + )?; + } + + Ok(()) +} + #[derive(Subcommand, Clone, Debug)] pub enum WizardSubcommands { /// Create a 256 bit secret and shard the secret to smart cards. @@ -209,6 +243,24 @@ pub enum WizardSubcommands { #[arg(long)] output: Option, }, + + /// Create a 256 bit secret and shard the secret to previously known OpenPGP certificates, + /// deriving the default OpenPGP certificate for the secret. + /// + /// This command was purpose-built for DEFCON and is not intended to be used normally, as it + /// implies keys used for sharding have been generated by a custom source. + BottomsUp { + /// The location of OpenPGP certificates to use when sharding. + key_discovery: PathBuf, + + /// The minimum amount of keys required to decrypt the secret. + #[arg(long)] + threshold: u8, + + /// The file to write the generated shard file to. + #[arg(long)] + output: Option, + }, } impl WizardSubcommands { @@ -220,6 +272,11 @@ impl WizardSubcommands { keys_per_shard, output, } => generate_shard_secret(*threshold, *max, *keys_per_shard, output), + WizardSubcommands::BottomsUp { + key_discovery, + threshold, + output, + } => bottoms_up(key_discovery, *threshold, output), } } }