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 7a78cd5..7c41238 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-combine-openpgp.rs @@ -7,6 +7,7 @@ use std::{ process::ExitCode, }; +use keyfork_prompt::{DefaultTerminal, default_terminal}; use keyfork_shard::{openpgp::OpenPGP, Format}; type Result> = std::result::Result; @@ -31,8 +32,10 @@ fn run() -> Result<()> { _ => panic!("Usage: {program_name} [key_discovery]"), }; - let openpgp = OpenPGP; - let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file)?; + let openpgp = OpenPGP::::new(); + let prompt_handler = default_terminal()?; + + let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?; 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 3b06219..491d0d2 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-decrypt-openpgp.rs @@ -7,6 +7,7 @@ use std::{ process::ExitCode, }; +use keyfork_prompt::{DefaultTerminal, default_terminal}; use keyfork_shard::{Format, openpgp::OpenPGP}; type Result> = std::result::Result; @@ -31,9 +32,10 @@ fn run() -> Result<()> { _ => panic!("Usage: {program_name} [key_discovery]"), }; - let openpgp = OpenPGP; + let openpgp = OpenPGP::::new(); + let prompt_handler = default_terminal()?; - openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file)?; + openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?; 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 3423ef6..8a4b3e3 100644 --- a/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs +++ b/crates/keyfork-shard/src/bin/keyfork-shard-split-openpgp.rs @@ -2,6 +2,7 @@ use std::{env, path::PathBuf, process::ExitCode, str::FromStr}; +use keyfork_prompt::terminal::DefaultTerminal; use keyfork_shard::{Format, openpgp::OpenPGP}; #[derive(Clone, Debug)] @@ -50,7 +51,7 @@ fn run() -> Result<()> { smex::decode(line?)? }; - let openpgp = OpenPGP; + let openpgp = OpenPGP::::new(); 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 d930bef..1f95657 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -1,6 +1,9 @@ #![doc = include_str!("../README.md")] -use std::io::{stdin, stdout, Read, Write}; +use std::{ + io::{stdin, stdout, Read, Write}, + sync::{Arc, Mutex}, +}; use aes_gcm::{ aead::{consts::U12, Aead, AeadCore, OsRng}, @@ -122,6 +125,7 @@ pub trait Format { &self, private_keys: Option, encrypted_messages: &[Self::EncryptedData], + prompt: Arc>, ) -> Result<(Vec, u8), Self::Error>; /// Decrypt a single share and associated metadata from a reaable input. For the current @@ -135,6 +139,7 @@ pub trait Format { &self, private_keys: Option, encrypted_data: &[Self::EncryptedData], + prompt: Arc>, ) -> Result<(Share, u8), Self::Error>; /// Decrypt multiple shares and combine them to recreate a secret. @@ -146,12 +151,17 @@ pub trait Format { &self, private_key_discovery: Option>, reader: impl Read + Send + Sync, + prompt: impl PromptHandler, ) -> Result, Box> { 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)?; + let (shares, threshold) = self.decrypt_all_shards( + private_keys, + &encrypted_messages, + Arc::new(Mutex::new(prompt)), + )?; let secret = Sharks(threshold) .recover(&shares) @@ -171,8 +181,9 @@ pub trait Format { &self, private_key_discovery: Option>, reader: impl Read + Send + Sync, + prompt: impl PromptHandler, ) -> Result<(), Box> { - let mut pm = Terminal::new(stdin(), stdout())?; + let prompt = Arc::new(Mutex::new(prompt)); // parse input let private_keys = private_key_discovery @@ -187,7 +198,10 @@ pub trait Format { // receive remote data via scanning QR code from camera #[cfg(feature = "qrcode")] { - pm.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?; + prompt + .lock() + .unwrap() + .prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?; if let Ok(Some(hex)) = keyfork_qrcode::scan_camera(std::time::Duration::from_secs(30), 0) { @@ -195,7 +209,10 @@ pub trait Format { nonce_data = Some(decoded_data[..12].try_into().map_err(|_| InvalidData)?); pubkey_data = Some(decoded_data[12..].try_into().map_err(|_| InvalidData)?) } else { - pm.prompt_message(PromptMessage::Text(QRCODE_ERROR.to_string()))?; + prompt + .lock() + .unwrap() + .prompt_message(PromptMessage::Text(QRCODE_ERROR.to_string()))?; }; } @@ -206,7 +223,9 @@ pub trait Format { let validator = MnemonicSetValidator { word_lengths: [9, 24], }; - let [nonce_mnemonic, pubkey_mnemonic] = pm + let [nonce_mnemonic, pubkey_mnemonic] = prompt + .lock() + .unwrap() .prompt_validated_wordlist::( QRCODE_COULDNT_READ, 3, @@ -237,7 +256,8 @@ pub trait Format { let shared_key = Aes256Gcm::new_from_slice(&hkdf_output)?; // decrypt a single shard and create the payload - let (share, threshold) = self.decrypt_one_shard(private_keys, &encrypted_messages)?; + let (share, threshold) = + self.decrypt_one_shard(private_keys, &encrypted_messages, prompt.clone())?; let mut payload = Vec::from(&share); payload.insert(0, HUNK_VERSION); payload.insert(1, threshold); @@ -285,7 +305,7 @@ pub trait Format { let mut qrcode_data = our_pubkey_mnemonic.to_bytes(); qrcode_data.extend(payload_mnemonic.as_bytes()); if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Highest) { - pm.prompt_message(PromptMessage::Text( + prompt.lock().unwrap().prompt_message(PromptMessage::Text( concat!( "A QR code will be displayed after this prompt. ", "Send the QR code back to the operator combining the shards. ", @@ -293,11 +313,17 @@ pub trait Format { ) .to_string(), ))?; - pm.prompt_message(PromptMessage::Data(qrcode))?; + prompt + .lock() + .unwrap() + .prompt_message(PromptMessage::Data(qrcode))?; } } - pm.prompt_message(PromptMessage::Text(format!( + prompt + .lock() + .unwrap() + .prompt_message(PromptMessage::Text(format!( "Upon request, these words should be sent: {our_pubkey_mnemonic} {payload_mnemonic}" )))?; diff --git a/crates/keyfork-shard/src/openpgp.rs b/crates/keyfork-shard/src/openpgp.rs index f689959..1ed9704 100644 --- a/crates/keyfork-shard/src/openpgp.rs +++ b/crates/keyfork-shard/src/openpgp.rs @@ -5,12 +5,15 @@ use std::{ io::{Read, Write}, path::Path, str::FromStr, + sync::{Arc, Mutex}, + marker::PhantomData, }; use keyfork_derive_openpgp::{ derive_util::{DerivationPath, VariableLengthSeed}, XPrv, }; +use keyfork_prompt::PromptHandler; use openpgp::{ armor::{Kind, Writer}, cert::{Cert, CertParser, ValidCert}, @@ -176,9 +179,20 @@ impl EncryptedMessage { } /// -pub struct OpenPGP; +pub struct OpenPGP { + p: PhantomData

, +} -impl OpenPGP { +impl OpenPGP

{ + #[allow(clippy::new_without_default, missing_docs)] + pub fn new() -> Self { + Self { + p: PhantomData, + } + } +} + +impl OpenPGP

{ /// 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. /// @@ -209,7 +223,7 @@ impl OpenPGP { } } -impl Format for OpenPGP { +impl Format for OpenPGP

{ type Error = Error; type PublicKey = Cert; type PrivateKeyData = Vec; @@ -400,12 +414,14 @@ impl Format for OpenPGP { &self, private_keys: Option, encrypted_data: &[Self::EncryptedData], + prompt: Arc>, ) -> std::result::Result<(Vec, u8), Self::Error> { // Be as liberal as possible when decrypting. // We don't want to invalidate someone's keys just because the old sig expired. let policy = NullPolicy::new(); - let mut keyring = Keyring::new(private_keys.unwrap_or_default())?; - let mut manager = SmartcardManager::new()?; + + let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?; + let mut manager = SmartcardManager::new(prompt.clone())?; let mut encrypted_messages = encrypted_data.iter(); @@ -457,10 +473,12 @@ impl Format for OpenPGP { &self, private_keys: Option, encrypted_data: &[Self::EncryptedData], + prompt: Arc>, ) -> std::result::Result<(Share, u8), Self::Error> { let policy = NullPolicy::new(); - let mut keyring = Keyring::new(private_keys.unwrap_or_default())?; - let mut manager = SmartcardManager::new()?; + + let mut keyring = Keyring::new(private_keys.unwrap_or_default(), prompt.clone())?; + let mut manager = SmartcardManager::new(prompt.clone())?; let mut encrypted_messages = encrypted_data.iter(); @@ -499,22 +517,22 @@ impl Format for OpenPGP { } } -impl KeyDiscovery for &Path { - fn discover_public_keys(&self) -> Result::PublicKey>> { - OpenPGP::discover_certs(self) +impl KeyDiscovery> for &Path { + fn discover_public_keys(&self) -> Result as Format>::PublicKey>> { + OpenPGP::

::discover_certs(self) } - fn discover_private_keys(&self) -> Result<::PrivateKeyData> { - todo!() + fn discover_private_keys(&self) -> Result< as Format>::PrivateKeyData> { + OpenPGP::

::discover_certs(self) } } -impl KeyDiscovery for &[Cert] { - fn discover_public_keys(&self) -> Result::PublicKey>> { +impl KeyDiscovery> for &[Cert] { + fn discover_public_keys(&self) -> Result as Format>::PublicKey>> { Ok(self.to_vec()) } - fn discover_private_keys(&self) -> Result<::PrivateKeyData> { + fn discover_private_keys(&self) -> Result< as Format>::PrivateKeyData> { Ok(self.to_vec()) } } @@ -575,12 +593,12 @@ fn decode_metadata_v1(buf: &[u8]) -> Result<(u8, Cert, Vec)> { // NOTE: When using single-decryptor mechanism, use this method with `threshold = 1` to return a // single message. -fn decrypt_with_manager( +fn decrypt_with_manager( threshold: u8, messages: &mut HashMap, certs: &[Cert], policy: &dyn Policy, - manager: &mut SmartcardManager, + manager: &mut SmartcardManager

, ) -> Result>> { let mut decrypted_messages = HashMap::new(); @@ -620,11 +638,11 @@ fn decrypt_with_manager( // NOTE: When using single-decryptor mechanism, only a single key should be provided in Keyring to // decrypt messages with. -fn decrypt_with_keyring( +fn decrypt_with_keyring( messages: &mut HashMap, certs: &[Cert], policy: &NullPolicy, - keyring: &mut Keyring, + keyring: &mut Keyring

, ) -> Result>, Error> { let mut decrypted_messages = HashMap::new(); @@ -654,11 +672,11 @@ fn decrypt_with_keyring( Ok(decrypted_messages) } -fn decrypt_metadata( +fn decrypt_metadata( message: &EncryptedMessage, policy: &NullPolicy, - keyring: &mut Keyring, - manager: &mut SmartcardManager, + keyring: &mut Keyring

, + manager: &mut SmartcardManager

, ) -> Result> { Ok(if keyring.is_empty() { manager.load_any_card()?; diff --git a/crates/keyfork-shard/src/openpgp/keyring.rs b/crates/keyfork-shard/src/openpgp/keyring.rs index 6cd571a..fdc9e91 100644 --- a/crates/keyfork-shard/src/openpgp/keyring.rs +++ b/crates/keyfork-shard/src/openpgp/keyring.rs @@ -1,4 +1,6 @@ -use keyfork_prompt::{Error as PromptError, DefaultTerminal, default_terminal, PromptHandler}; +use std::sync::{Arc, Mutex}; + +use keyfork_prompt::{Error as PromptError, PromptHandler}; use super::openpgp::{ self, @@ -22,18 +24,18 @@ pub enum Error { pub type Result = std::result::Result; -pub struct Keyring { +pub struct Keyring { full_certs: Vec, root: Option, - pm: DefaultTerminal, + pm: Arc>, } -impl Keyring { - pub fn new(certs: impl AsRef<[Cert]>) -> Result { +impl Keyring

{ + pub fn new(certs: impl AsRef<[Cert]>, p: Arc>) -> Result { Ok(Self { full_certs: certs.as_ref().to_vec(), root: Default::default(), - pm: default_terminal()?, + pm: p, }) } @@ -57,7 +59,7 @@ impl Keyring { } } -impl VerificationHelper for &mut Keyring { +impl VerificationHelper for &mut Keyring

{ fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result> { Ok(ids .iter() @@ -93,7 +95,7 @@ impl VerificationHelper for &mut Keyring { } } -impl DecryptionHelper for &mut Keyring { +impl DecryptionHelper for &mut Keyring

{ fn decrypt( &mut self, pkesks: &[PKESK], @@ -137,6 +139,8 @@ impl DecryptionHelper for &mut Keyring { }; let passphrase = self .pm + .lock() + .unwrap() .prompt_passphrase(&message) .context("Decryption passphrase")?; secret_key diff --git a/crates/keyfork-shard/src/openpgp/smartcard.rs b/crates/keyfork-shard/src/openpgp/smartcard.rs index 4794db3..98cb1d1 100644 --- a/crates/keyfork-shard/src/openpgp/smartcard.rs +++ b/crates/keyfork-shard/src/openpgp/smartcard.rs @@ -1,9 +1,11 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + sync::{Arc, Mutex}, +}; use keyfork_prompt::{ - default_terminal, validators::{PinValidator, Validator}, - DefaultTerminal, Error as PromptError, Message, PromptHandler, + Error as PromptError, Message, PromptHandler, }; use super::openpgp::{ @@ -66,19 +68,19 @@ fn format_name(input: impl AsRef) -> String { } #[allow(clippy::module_name_repetitions)] -pub struct SmartcardManager { +pub struct SmartcardManager { current_card: Option>, root: Option, - pm: DefaultTerminal, + pm: Arc>, pin_cache: HashMap, } -impl SmartcardManager { - pub fn new() -> Result { +impl SmartcardManager

{ + pub fn new(p: Arc>) -> Result { Ok(Self { current_card: None, root: None, - pm: default_terminal()?, + pm: p, pin_cache: Default::default(), }) } @@ -96,9 +98,13 @@ impl SmartcardManager { if let Some(c) = PcscBackend::cards(None)?.next().transpose()? { break c; } - self.pm.prompt_message(Message::Text( - "No smart card was found. Please plug in a smart card and press enter".to_string(), - ))?; + self.pm + .lock() + .unwrap() + .prompt_message(Message::Text( + "No smart card was found. Please plug in a smart card and press enter" + .to_string(), + ))?; }; let mut card = Card::::new(card_backend).map_err(Error::OpenSmartCard)?; let transaction = card.transaction().map_err(Error::Transaction)?; @@ -152,16 +158,19 @@ impl SmartcardManager { } } - self.pm.prompt_message(Message::Text( - "Please plug in a smart card and press enter".to_string(), - ))?; + self.pm + .lock() + .unwrap() + .prompt_message(Message::Text( + "Please plug in a smart card and press enter".to_string(), + ))?; } Ok(None) } } -impl VerificationHelper for &mut SmartcardManager { +impl VerificationHelper for &mut SmartcardManager

{ fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result> { #[allow(clippy::flat_map_option)] Ok(ids @@ -194,7 +203,7 @@ impl VerificationHelper for &mut SmartcardManager { } } -impl DecryptionHelper for &mut SmartcardManager { +impl DecryptionHelper for &mut SmartcardManager

{ fn decrypt( &mut self, pkesks: &[PKESK], @@ -254,6 +263,8 @@ impl DecryptionHelper for &mut SmartcardManager { }; let temp_pin = self .pm + .lock() + .unwrap() .prompt_validated_passphrase(&message, 3, &pin_validator)?; let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim()); match verification_status { @@ -265,6 +276,8 @@ impl DecryptionHelper for &mut SmartcardManager { // NOTE: This should not be hit, because of the above validator. Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => { self.pm + .lock() + .unwrap() .prompt_message(Message::Text("Invalid PIN length entered.".to_string()))?; } Err(_) => {} diff --git a/crates/keyfork/src/cli/recover.rs b/crates/keyfork/src/cli/recover.rs index 2a0339a..6a9b655 100644 --- a/crates/keyfork/src/cli/recover.rs +++ b/crates/keyfork/src/cli/recover.rs @@ -3,6 +3,7 @@ use clap::{Parser, Subcommand}; use std::path::PathBuf; use keyfork_mnemonic_util::{English, Mnemonic}; +use keyfork_prompt::{default_terminal, DefaultTerminal}; use keyfork_shard::{remote_decrypt, Format}; type Result> = std::result::Result; @@ -34,10 +35,14 @@ impl RecoverSubcommands { } => { let content = std::fs::read_to_string(shard_file)?; if content.contains("BEGIN PGP MESSAGE") { - let openpgp = keyfork_shard::openpgp::OpenPGP; + let openpgp = keyfork_shard::openpgp::OpenPGP::::new(); + let prompt_handler = default_terminal()?; // TODO: remove .clone() by making handle() consume self - let seed = openpgp - .decrypt_all_shards_to_secret(key_discovery.as_deref(), content.as_bytes())?; + let seed = openpgp.decrypt_all_shards_to_secret( + key_discovery.as_deref(), + content.as_bytes(), + prompt_handler, + )?; Ok(seed) } else { panic!("unknown format of shard file"); @@ -50,7 +55,6 @@ impl RecoverSubcommands { } RecoverSubcommands::Mnemonic {} => { use keyfork_prompt::{ - default_terminal, validators::{ mnemonic::{MnemonicChoiceValidator, WordLength}, Validator, diff --git a/crates/keyfork/src/cli/shard.rs b/crates/keyfork/src/cli/shard.rs index 0fcc04e..5f9e2b4 100644 --- a/crates/keyfork/src/cli/shard.rs +++ b/crates/keyfork/src/cli/shard.rs @@ -1,5 +1,6 @@ use super::Keyfork; use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum}; +use keyfork_prompt::{default_terminal, DefaultTerminal}; use keyfork_shard::Format as _; use std::{ io::{stdin, stdout, Read, Write}, @@ -63,7 +64,7 @@ impl ShardExec for OpenPGP { secret: &[u8], output: &mut (impl Write + Send + Sync), ) -> Result<(), Box> { - let opgp = keyfork_shard::openpgp::OpenPGP; + let opgp = keyfork_shard::openpgp::OpenPGP::::new(); opgp.shard_and_encrypt(threshold, max, secret, key_discovery, output) } @@ -72,10 +73,10 @@ impl ShardExec for OpenPGP { key_discovery: Option<&Path>, input: impl Read + Send + Sync, output: &mut impl Write, - ) -> Result<(), Box> - { - let openpgp = keyfork_shard::openpgp::OpenPGP; - let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input)?; + ) -> Result<(), Box> { + let openpgp = keyfork_shard::openpgp::OpenPGP::::new(); + let prompt = default_terminal()?; + let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input, prompt)?; write!(output, "{}", smex::encode(bytes))?; Ok(()) @@ -85,10 +86,10 @@ impl ShardExec for OpenPGP { &self, key_discovery: Option<&Path>, input: impl Read + Send + Sync, - ) -> Result<(), Box> - { - let openpgp = keyfork_shard::openpgp::OpenPGP; - openpgp.decrypt_one_shard_for_transport(key_discovery, input)?; + ) -> Result<(), Box> { + let openpgp = keyfork_shard::openpgp::OpenPGP::::new(); + let prompt = default_terminal()?; + openpgp.decrypt_one_shard_for_transport(key_discovery, input, prompt)?; Ok(()) } } diff --git a/crates/keyfork/src/cli/wizard.rs b/crates/keyfork/src/cli/wizard.rs index 3509c62..abdbed5 100644 --- a/crates/keyfork/src/cli/wizard.rs +++ b/crates/keyfork/src/cli/wizard.rs @@ -12,7 +12,7 @@ use keyfork_derive_openpgp::{ use keyfork_derive_util::{DerivationIndex, DerivationPath}; use keyfork_prompt::{ validators::{PinValidator, Validator}, - Message, PromptHandler, Terminal, + Message, PromptHandler, DefaultTerminal, default_terminal }; use keyfork_shard::{Format, openpgp::OpenPGP}; @@ -105,7 +105,7 @@ fn generate_shard_secret( output_file: &Option, ) -> Result<()> { let seed = keyfork_entropy::generate_entropy_of_const_size::<{256 / 8}>()?; - let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?; + let mut pm = default_terminal()?; let mut certs = vec![]; let mut seen_cards: HashSet = HashSet::new(); let stdout = std::io::stdout(); @@ -165,7 +165,7 @@ fn generate_shard_secret( certs.push(cert); } - let opgp = OpenPGP; + let opgp = OpenPGP::::new(); if let Some(output_file) = output_file { let output = File::create(output_file)?;