From b75d45876aa10ebe3ea8d5782c67dbd6d17679ff Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 18 Feb 2024 20:19:29 -0500 Subject: [PATCH] keyfork-shard: refactor key discovery mechanisms --- .../src/bin/keyfork-shard-combine-openpgp.rs | 4 +- .../src/bin/keyfork-shard-decrypt-openpgp.rs | 2 +- .../src/bin/keyfork-shard-split-openpgp.rs | 2 +- crates/keyfork-shard/src/lib.rs | 64 +++++++++---------- crates/keyfork-shard/src/openpgp.rs | 36 ++++++----- crates/keyfork-shard/src/openpgp/keyring.rs | 4 +- crates/keyfork/src/cli/recover.rs | 2 +- crates/keyfork/src/cli/shard.rs | 36 ++++------- crates/keyfork/src/cli/wizard.rs | 10 +-- 9 files changed, 77 insertions(+), 83 deletions(-) diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs b/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs index f910647..82eb167 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs @@ -7,7 +7,7 @@ use std::{ process::ExitCode, }; -use keyfork_shard::{Format, openpgp::OpenPGP}; +use keyfork_shard::{openpgp::OpenPGP, Format}; type Result> = std::result::Result; @@ -32,7 +32,7 @@ fn run() -> Result<()> { }; let openpgp = OpenPGP; - let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, messages_file)?; + let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file)?; print!("{}", smex::encode(&bytes)); Ok(()) diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs b/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs index 2393a1b..3b06219 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs @@ -33,7 +33,7 @@ fn run() -> Result<()> { let openpgp = OpenPGP; - openpgp.decrypt_one_shard_for_transport(key_discovery, messages_file)?; + openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file)?; Ok(()) } diff --git a/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs b/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs index dc2cdd7..3423ef6 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs @@ -52,7 +52,7 @@ fn run() -> Result<()> { let openpgp = OpenPGP; - openpgp.shard_and_encrypt(threshold, max, &input, key_discovery, std::io::stdout())?; + openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?; Ok(()) } diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index 0fa7d22..4fbecaa 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -1,9 +1,6 @@ #![doc = include_str!("../README.md")] -use std::{ - io::{stdin, stdout, Read, Write}, - path::Path, -}; +use std::io::{stdin, stdout, Read, Write}; use aes_gcm::{ aead::{consts::U12, Aead, AeadCore, OsRng}, @@ -25,6 +22,27 @@ const ENC_LEN: u8 = 4 * 16; #[cfg(feature = "openpgp")] pub mod openpgp; +/// A trait to specify where keys can be discovered from, such as a Rust-native type or a path on +/// the filesystem that keys may be read from. +pub trait KeyDiscovery { + /// Discover public keys for the associated format. + /// + /// # Errors + /// The method may return an error if public keys could not be loaded from the given discovery + /// mechanism. A discovery mechanism _must_ be able to detect public keys. + fn discover_public_keys(&self) -> Result, F::Error>; + + /// Discover private keys for the associated format. + /// + /// # Errors + /// The method may return an error if private keys could not be loaded from the given + /// discovery mechanism. Keys may exist off-system (such as with smartcards), in which case the + /// PrivateKeyData type of the asssociated format should be either `()` (if the keys may never + /// exist on-system) or an empty container (such as an empty Vec); in either case, this method + /// _must not_ return an error if keys are accessible but can't be transferred into memory. + fn discover_private_keys(&self) -> Result; +} + /// A format to use for splitting and combining secrets. pub trait Format { /// The error type returned from any failed operations. @@ -42,17 +60,6 @@ pub trait Format { /// A type representing the parsed, but encrypted, Shard data. type EncryptedData; - /// Parse the public key data from a readable type. - /// - /// # Errors - /// The method may return an error if private key data could not be properly parsed from the - /// path. - /// occurred while parsing the public key data. - fn parse_public_key_data( - &self, - key_data_path: impl AsRef, - ) -> Result, Self::Error>; - /// Derive a signer fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey; @@ -83,17 +90,6 @@ pub trait Format { signing_key: &mut Self::SigningKey, ) -> Result; - /// Parse the private key data from a readable type. The private key may not be accessible (it - /// may be hardware only, such as a smartcard), for which this method may return None. - /// - /// # Errors - /// The method may return an error if private key data could not be properly parsed from the - /// path. - fn parse_private_key_data( - &self, - key_data_path: impl AsRef, - ) -> Result; - /// Parse the Shard file into a processable type. /// /// # Errors @@ -148,11 +144,11 @@ pub trait Format { /// be combined into a secret. fn decrypt_all_shards_to_secret( &self, - private_key_data_path: Option>, + private_key_discovery: Option>, reader: impl Read + Send + Sync, ) -> Result, Box> { - let private_keys = private_key_data_path - .map(|p| self.parse_private_key_data(p)) + let private_keys = private_key_discovery + .map(|p| p.discover_private_keys()) .transpose()?; let encrypted_messages = self.parse_shard_file(reader)?; let (shares, threshold) = self.decrypt_all_shards(private_keys, &encrypted_messages)?; @@ -173,15 +169,15 @@ pub trait Format { /// QR code; instead, a mnemonic prompt will be used. fn decrypt_one_shard_for_transport( &self, - private_key_data_path: Option>, + private_key_discovery: Option>, reader: impl Read + Send + Sync, ) -> Result<(), Box> { let mut pm = Terminal::new(stdin(), stdout())?; let wordlist = Wordlist::default(); // parse input - let private_keys = private_key_data_path - .map(|p| self.parse_private_key_data(p)) + let private_keys = private_key_discovery + .map(|p| p.discover_private_keys()) .transpose()?; let encrypted_messages = self.parse_shard_file(reader)?; @@ -321,7 +317,7 @@ pub trait Format { threshold: u8, max: u8, secret: &[u8], - public_key_data_path: impl AsRef, + public_key_discovery: impl KeyDiscovery, writer: impl Write + Send + Sync, ) -> Result<(), Box> { let mut signing_key = self.derive_signing_key(secret); @@ -329,7 +325,7 @@ pub trait Format { let sharks = Sharks(threshold); let dealer = sharks.dealer(secret); - let public_keys = self.parse_public_key_data(public_key_data_path)?; + let public_keys = public_key_discovery.discover_public_keys()?; assert!( public_keys.len() < u8::MAX as usize, "must have less than u8::MAX public keys" diff --git a/crates/keyfork-shard/src/openpgp.rs b/crates/keyfork-shard/src/openpgp.rs index a6c356c..5d5d061 100644 --- a/crates/keyfork-shard/src/openpgp.rs +++ b/crates/keyfork-shard/src/openpgp.rs @@ -58,7 +58,7 @@ const SHARD_METADATA_OFFSET: usize = 2; use super::{ Format, InvalidData, SharksError, HUNK_VERSION, QRCODE_COULDNT_READ, QRCODE_ERROR, - QRCODE_PROMPT, QRCODE_TIMEOUT, + QRCODE_PROMPT, QRCODE_TIMEOUT, KeyDiscovery }; // 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding @@ -282,13 +282,6 @@ impl Format for OpenPGP { type SigningKey = Cert; type EncryptedData = EncryptedMessage; - fn parse_public_key_data( - &self, - key_data_path: impl AsRef, - ) -> std::result::Result, Self::Error> { - Self::discover_certs(key_data_path) - } - /// Derive an OpenPGP Shard certificate from the given seed. fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey { let seed = VariableLengthSeed::new(seed); @@ -449,13 +442,6 @@ impl Format for OpenPGP { Ok(message) } - fn parse_private_key_data( - &self, - key_data_path: impl AsRef, - ) -> std::result::Result { - Self::discover_certs(key_data_path) - } - fn parse_shard_file( &self, shard_file: impl Read + Send + Sync, @@ -579,6 +565,26 @@ impl Format for OpenPGP { } } +impl KeyDiscovery for &Path { + fn discover_public_keys(&self) -> Result::PublicKey>> { + OpenPGP::discover_certs(self) + } + + fn discover_private_keys(&self) -> Result<::PrivateKeyData> { + todo!() + } +} + +impl KeyDiscovery for &[Cert] { + fn discover_public_keys(&self) -> Result::PublicKey>> { + Ok(self.to_vec()) + } + + fn discover_private_keys(&self) -> Result<::PrivateKeyData> { + Ok(self.to_vec()) + } +} + /// Read all OpenPGP certificates in a path and return a [`Vec`] of them. Certificates are read /// from a file, or from files one level deep in a directory. /// diff --git a/crates/keyfork-shard/src/openpgp/keyring.rs b/crates/keyfork-shard/src/openpgp/keyring.rs index fd01d89..6cd571a 100644 --- a/crates/keyfork-shard/src/openpgp/keyring.rs +++ b/crates/keyfork-shard/src/openpgp/keyring.rs @@ -111,12 +111,10 @@ impl DecryptionHelper for &mut Keyring { pkesk.recipient().is_wildcard() || cert.keys().any(|k| &k.keyid() == pkesk.recipient()) }) { - #[allow(deprecated, clippy::map_flatten)] let name = cert .userids() .next() - .map(|userid| userid.userid().name().transpose()) - .flatten() + .and_then(|userid| userid.userid().name2().transpose()) .transpose() .ok() .flatten(); diff --git a/crates/keyfork/src/cli/recover.rs b/crates/keyfork/src/cli/recover.rs index 3b766b2..2a0339a 100644 --- a/crates/keyfork/src/cli/recover.rs +++ b/crates/keyfork/src/cli/recover.rs @@ -37,7 +37,7 @@ impl RecoverSubcommands { let openpgp = keyfork_shard::openpgp::OpenPGP; // TODO: remove .clone() by making handle() consume self let seed = openpgp - .decrypt_all_shards_to_secret(key_discovery.clone(), content.as_bytes())?; + .decrypt_all_shards_to_secret(key_discovery.as_deref(), content.as_bytes())?; Ok(seed) } else { panic!("unknown format of shard file"); diff --git a/crates/keyfork/src/cli/shard.rs b/crates/keyfork/src/cli/shard.rs index dc91468..2d441b8 100644 --- a/crates/keyfork/src/cli/shard.rs +++ b/crates/keyfork/src/cli/shard.rs @@ -32,27 +32,23 @@ trait ShardExec { &self, threshold: u8, max: u8, - key_discovery: impl AsRef, + key_discovery: &Path, secret: &[u8], output: &mut (impl Write + Send + Sync), ) -> Result<(), Box>; - fn combine( + fn combine( &self, - key_discovery: Option, + key_discovery: Option<&Path>, input: impl Read + Send + Sync, output: &mut impl Write, - ) -> Result<(), Box> - where - T: AsRef; + ) -> Result<(), Box>; - fn decrypt( + fn decrypt( &self, - key_discovery: Option, + key_discovery: Option<&Path>, input: impl Read + Send + Sync, - ) -> Result<(), Box> - where - T: AsRef; + ) -> Result<(), Box>; } #[derive(Clone, Debug)] @@ -63,7 +59,7 @@ impl ShardExec for OpenPGP { &self, threshold: u8, max: u8, - key_discovery: impl AsRef, + key_discovery: &Path, secret: &[u8], output: &mut (impl Write + Send + Sync), ) -> Result<(), Box> { @@ -71,14 +67,12 @@ impl ShardExec for OpenPGP { opgp.shard_and_encrypt(threshold, max, secret, key_discovery, output) } - fn combine( + fn combine( &self, - key_discovery: Option, + key_discovery: Option<&Path>, input: impl Read + Send + Sync, output: &mut impl Write, ) -> Result<(), Box> - where - T: AsRef, { let openpgp = keyfork_shard::openpgp::OpenPGP; let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input)?; @@ -87,13 +81,11 @@ impl ShardExec for OpenPGP { Ok(()) } - fn decrypt( + fn decrypt( &self, - key_discovery: Option, + key_discovery: Option<&Path>, input: impl Read + Send + Sync, ) -> Result<(), Box> - where - T: AsRef, { let openpgp = keyfork_shard::openpgp::OpenPGP; openpgp.decrypt_one_shard_for_transport(key_discovery, input)?; @@ -189,7 +181,7 @@ impl ShardSubcommands { match format { Some(Format::OpenPGP(o)) => { - o.decrypt(key_discovery.as_ref(), shard_content.as_bytes()) + o.decrypt(key_discovery.as_deref(), shard_content.as_bytes()) } Some(Format::P256(_p)) => todo!(), None => panic!("{COULD_NOT_DETERMINE_FORMAT}"), @@ -206,7 +198,7 @@ impl ShardSubcommands { match format { Some(Format::OpenPGP(o)) => o.combine( - key_discovery.as_ref(), + key_discovery.as_deref(), shard_content.as_bytes(), &mut stdout, ), diff --git a/crates/keyfork/src/cli/wizard.rs b/crates/keyfork/src/cli/wizard.rs index e7a7b79..3509c62 100644 --- a/crates/keyfork/src/cli/wizard.rs +++ b/crates/keyfork/src/cli/wizard.rs @@ -15,6 +15,8 @@ use keyfork_prompt::{ Message, PromptHandler, Terminal, }; +use keyfork_shard::{Format, openpgp::OpenPGP}; + #[derive(thiserror::Error, Debug)] #[error("Invalid PIN length: {0}")] pub struct PinLength(usize); @@ -163,13 +165,13 @@ fn generate_shard_secret( certs.push(cert); } + let opgp = OpenPGP; + if let Some(output_file) = output_file { let output = File::create(output_file)?; - #[allow(deprecated)] - keyfork_shard::openpgp::split(threshold, certs, &seed, output)?; + opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], output)?; } else { - #[allow(deprecated)] - keyfork_shard::openpgp::split(threshold, certs, &seed, std::io::stdout())?; + opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], std::io::stdout())?; } Ok(()) }