keyfork-shard: first pass of reusable prompthandler

This commit is contained in:
Ryan Heywood 2024-02-20 18:33:54 -05:00
parent b15d088905
commit 354eae5a6a
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
10 changed files with 149 additions and 77 deletions

View File

@ -7,6 +7,7 @@ use std::{
process::ExitCode,
};
use keyfork_prompt::{DefaultTerminal, default_terminal};
use keyfork_shard::{openpgp::OpenPGP, Format};
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -31,8 +32,10 @@ fn run() -> Result<()> {
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
};
let openpgp = OpenPGP;
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file)?;
let openpgp = OpenPGP::<DefaultTerminal>::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(())

View File

@ -7,6 +7,7 @@ use std::{
process::ExitCode,
};
use keyfork_prompt::{DefaultTerminal, default_terminal};
use keyfork_shard::{Format, openpgp::OpenPGP};
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -31,9 +32,10 @@ fn run() -> Result<()> {
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
};
let openpgp = OpenPGP;
let openpgp = OpenPGP::<DefaultTerminal>::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(())
}

View File

@ -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::<DefaultTerminal>::new();
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
Ok(())

View File

@ -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<Self::PrivateKeyData>,
encrypted_messages: &[Self::EncryptedData],
prompt: Arc<Mutex<impl PromptHandler>>,
) -> Result<(Vec<Share>, 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<Self::PrivateKeyData>,
encrypted_data: &[Self::EncryptedData],
prompt: Arc<Mutex<impl PromptHandler>>,
) -> 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<impl KeyDiscovery<Self>>,
reader: impl Read + Send + Sync,
prompt: impl PromptHandler,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
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<impl KeyDiscovery<Self>>,
reader: impl Read + Send + Sync,
prompt: impl PromptHandler,
) -> Result<(), Box<dyn std::error::Error>> {
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::<English, _>(
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}"
)))?;

View File

@ -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: PromptHandler> {
p: PhantomData<P>,
}
impl OpenPGP {
impl<P: PromptHandler> OpenPGP<P> {
#[allow(clippy::new_without_default, missing_docs)]
pub fn new() -> Self {
Self {
p: PhantomData,
}
}
}
impl<P: PromptHandler> OpenPGP<P> {
/// 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<P: PromptHandler> Format for OpenPGP<P> {
type Error = Error;
type PublicKey = Cert;
type PrivateKeyData = Vec<Cert>;
@ -400,12 +414,14 @@ impl Format for OpenPGP {
&self,
private_keys: Option<Self::PrivateKeyData>,
encrypted_data: &[Self::EncryptedData],
prompt: Arc<Mutex<impl PromptHandler>>,
) -> std::result::Result<(Vec<Share>, 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<Self::PrivateKeyData>,
encrypted_data: &[Self::EncryptedData],
prompt: Arc<Mutex<impl PromptHandler>>,
) -> 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<OpenPGP> for &Path {
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
OpenPGP::discover_certs(self)
impl<P: PromptHandler> KeyDiscovery<OpenPGP<P>> for &Path {
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP<P> as Format>::PublicKey>> {
OpenPGP::<P>::discover_certs(self)
}
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
todo!()
fn discover_private_keys(&self) -> Result<<OpenPGP<P> as Format>::PrivateKeyData> {
OpenPGP::<P>::discover_certs(self)
}
}
impl KeyDiscovery<OpenPGP> for &[Cert] {
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
impl<P: PromptHandler> KeyDiscovery<OpenPGP<P>> for &[Cert] {
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP<P> as Format>::PublicKey>> {
Ok(self.to_vec())
}
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
fn discover_private_keys(&self) -> Result<<OpenPGP<P> as Format>::PrivateKeyData> {
Ok(self.to_vec())
}
}
@ -575,12 +593,12 @@ fn decode_metadata_v1(buf: &[u8]) -> Result<(u8, Cert, Vec<Cert>)> {
// 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<P: PromptHandler>(
threshold: u8,
messages: &mut HashMap<KeyID, EncryptedMessage>,
certs: &[Cert],
policy: &dyn Policy,
manager: &mut SmartcardManager,
manager: &mut SmartcardManager<P>,
) -> Result<HashMap<KeyID, Vec<u8>>> {
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<P: PromptHandler>(
messages: &mut HashMap<KeyID, EncryptedMessage>,
certs: &[Cert],
policy: &NullPolicy,
keyring: &mut Keyring,
keyring: &mut Keyring<P>,
) -> Result<HashMap<KeyID, Vec<u8>>, Error> {
let mut decrypted_messages = HashMap::new();
@ -654,11 +672,11 @@ fn decrypt_with_keyring(
Ok(decrypted_messages)
}
fn decrypt_metadata(
fn decrypt_metadata<P: PromptHandler>(
message: &EncryptedMessage,
policy: &NullPolicy,
keyring: &mut Keyring,
manager: &mut SmartcardManager,
keyring: &mut Keyring<P>,
manager: &mut SmartcardManager<P>,
) -> Result<Vec<u8>> {
Ok(if keyring.is_empty() {
manager.load_any_card()?;

View File

@ -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<T, E = Error> = std::result::Result<T, E>;
pub struct Keyring {
pub struct Keyring<P: PromptHandler> {
full_certs: Vec<Cert>,
root: Option<Cert>,
pm: DefaultTerminal,
pm: Arc<Mutex<P>>,
}
impl Keyring {
pub fn new(certs: impl AsRef<[Cert]>) -> Result<Self> {
impl<P: PromptHandler> Keyring<P> {
pub fn new(certs: impl AsRef<[Cert]>, p: Arc<Mutex<P>>) -> Result<Self> {
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<P: PromptHandler> VerificationHelper for &mut Keyring<P> {
fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
Ok(ids
.iter()
@ -93,7 +95,7 @@ impl VerificationHelper for &mut Keyring {
}
}
impl DecryptionHelper for &mut Keyring {
impl<P: PromptHandler> DecryptionHelper for &mut Keyring<P> {
fn decrypt<D>(
&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

View File

@ -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<str>) -> String {
}
#[allow(clippy::module_name_repetitions)]
pub struct SmartcardManager {
pub struct SmartcardManager<P: PromptHandler> {
current_card: Option<Card<Open>>,
root: Option<Cert>,
pm: DefaultTerminal,
pm: Arc<Mutex<P>>,
pin_cache: HashMap<Fingerprint, String>,
}
impl SmartcardManager {
pub fn new() -> Result<Self> {
impl<P: PromptHandler> SmartcardManager<P> {
pub fn new(p: Arc<Mutex<P>>) -> Result<Self> {
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::<Open>::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<P: PromptHandler> VerificationHelper for &mut SmartcardManager<P> {
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
#[allow(clippy::flat_map_option)]
Ok(ids
@ -194,7 +203,7 @@ impl VerificationHelper for &mut SmartcardManager {
}
}
impl DecryptionHelper for &mut SmartcardManager {
impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
fn decrypt<D>(
&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(_) => {}

View File

@ -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<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -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::<DefaultTerminal>::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,

View File

@ -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<dyn std::error::Error>> {
let opgp = keyfork_shard::openpgp::OpenPGP;
let opgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::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<dyn std::error::Error>>
{
let openpgp = keyfork_shard::openpgp::OpenPGP;
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input)?;
) -> Result<(), Box<dyn std::error::Error>> {
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::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<dyn std::error::Error>>
{
let openpgp = keyfork_shard::openpgp::OpenPGP;
openpgp.decrypt_one_shard_for_transport(key_discovery, input)?;
) -> Result<(), Box<dyn std::error::Error>> {
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
let prompt = default_terminal()?;
openpgp.decrypt_one_shard_for_transport(key_discovery, input, prompt)?;
Ok(())
}
}

View File

@ -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<PathBuf>,
) -> 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<String> = HashSet::new();
let stdout = std::io::stdout();
@ -165,7 +165,7 @@ fn generate_shard_secret(
certs.push(cert);
}
let opgp = OpenPGP;
let opgp = OpenPGP::<DefaultTerminal>::new();
if let Some(output_file) = output_file {
let output = File::create(output_file)?;