Compare commits
3 Commits
d7bf3d16e1
...
c46f9e48b7
Author | SHA1 | Date |
---|---|---|
|
c46f9e48b7 | |
|
f8db8702ce | |
|
92dde3dcee |
|
@ -11,7 +11,7 @@ pub struct BincodeLayer<'a, Request> {
|
||||||
phantom_request: PhantomData<&'a Request>,
|
phantom_request: PhantomData<&'a Request>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Request> BincodeLayer<'a, Request> {
|
impl<Request> BincodeLayer<'_, Request> {
|
||||||
/// Create a new [`BincodeLayer`].
|
/// Create a new [`BincodeLayer`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -21,7 +21,7 @@ impl<'a, Request> BincodeLayer<'a, Request> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Request> Default for BincodeLayer<'a, Request> {
|
impl<Request> Default for BincodeLayer<'_, Request> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
process::ExitCode,
|
process::ExitCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_prompt::{DefaultTerminal, default_terminal};
|
use keyfork_prompt::default_handler;
|
||||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
@ -32,8 +32,8 @@ fn run() -> Result<()> {
|
||||||
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
|
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let openpgp = OpenPGP::<DefaultTerminal>::new();
|
let openpgp = OpenPGP;
|
||||||
let prompt_handler = default_terminal()?;
|
let prompt_handler = default_handler()?;
|
||||||
|
|
||||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
||||||
print!("{}", smex::encode(bytes));
|
print!("{}", smex::encode(bytes));
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
process::ExitCode,
|
process::ExitCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_prompt::{DefaultTerminal, default_terminal};
|
use keyfork_prompt::default_handler;
|
||||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
@ -32,8 +32,8 @@ fn run() -> Result<()> {
|
||||||
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
|
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let openpgp = OpenPGP::<DefaultTerminal>::new();
|
let openpgp = OpenPGP;
|
||||||
let prompt_handler = default_terminal()?;
|
let prompt_handler = default_handler()?;
|
||||||
|
|
||||||
openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
|
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
|
||||||
|
|
||||||
use keyfork_prompt::terminal::DefaultTerminal;
|
|
||||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -51,7 +50,7 @@ fn run() -> Result<()> {
|
||||||
smex::decode(line?)?
|
smex::decode(line?)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let openpgp = OpenPGP::<DefaultTerminal>::new();
|
let openpgp = OpenPGP;
|
||||||
|
|
||||||
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
|
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Read, Write},
|
io::{stdin, stdout, Read, Write},
|
||||||
sync::{Arc, Mutex},
|
sync::Mutex,
|
||||||
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use aes_gcm::{
|
use aes_gcm::{
|
||||||
|
@ -11,10 +12,12 @@ use aes_gcm::{
|
||||||
Aes256Gcm, KeyInit, Nonce,
|
Aes256Gcm, KeyInit, Nonce,
|
||||||
};
|
};
|
||||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||||
|
use blahaj::{Share, Sharks};
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||||
use keyfork_mnemonic::{English, Mnemonic};
|
use keyfork_mnemonic::{English, Mnemonic};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
|
prompt_validated_wordlist,
|
||||||
validators::{
|
validators::{
|
||||||
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
||||||
Validator,
|
Validator,
|
||||||
|
@ -22,7 +25,6 @@ use keyfork_prompt::{
|
||||||
Message as PromptMessage, PromptHandler, Terminal,
|
Message as PromptMessage, PromptHandler, Terminal,
|
||||||
};
|
};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use blahaj::{Share, Sharks};
|
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
const PLAINTEXT_LENGTH: u8 = 32 // shard
|
const PLAINTEXT_LENGTH: u8 = 32 // shard
|
||||||
|
@ -135,7 +137,7 @@ pub trait Format {
|
||||||
&self,
|
&self,
|
||||||
private_keys: Option<Self::PrivateKeyData>,
|
private_keys: Option<Self::PrivateKeyData>,
|
||||||
encrypted_messages: &[Self::EncryptedData],
|
encrypted_messages: &[Self::EncryptedData],
|
||||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||||
) -> Result<(Vec<Share>, u8), Self::Error>;
|
) -> Result<(Vec<Share>, u8), Self::Error>;
|
||||||
|
|
||||||
/// Decrypt a single share and associated metadata from a reaable input. For the current
|
/// Decrypt a single share and associated metadata from a reaable input. For the current
|
||||||
|
@ -149,7 +151,7 @@ pub trait Format {
|
||||||
&self,
|
&self,
|
||||||
private_keys: Option<Self::PrivateKeyData>,
|
private_keys: Option<Self::PrivateKeyData>,
|
||||||
encrypted_data: &[Self::EncryptedData],
|
encrypted_data: &[Self::EncryptedData],
|
||||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||||
) -> Result<(Share, u8), Self::Error>;
|
) -> Result<(Share, u8), Self::Error>;
|
||||||
|
|
||||||
/// Decrypt multiple shares and combine them to recreate a secret.
|
/// Decrypt multiple shares and combine them to recreate a secret.
|
||||||
|
@ -161,7 +163,7 @@ pub trait Format {
|
||||||
&self,
|
&self,
|
||||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||||
reader: impl Read + Send + Sync,
|
reader: impl Read + Send + Sync,
|
||||||
prompt: impl PromptHandler,
|
prompt: Box<dyn PromptHandler>,
|
||||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
let private_keys = private_key_discovery
|
let private_keys = private_key_discovery
|
||||||
.map(|p| p.discover_private_keys())
|
.map(|p| p.discover_private_keys())
|
||||||
|
@ -170,7 +172,7 @@ pub trait Format {
|
||||||
let (shares, threshold) = self.decrypt_all_shards(
|
let (shares, threshold) = self.decrypt_all_shards(
|
||||||
private_keys,
|
private_keys,
|
||||||
&encrypted_messages,
|
&encrypted_messages,
|
||||||
Arc::new(Mutex::new(prompt)),
|
Rc::new(Mutex::new(prompt)),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let secret = Sharks(threshold)
|
let secret = Sharks(threshold)
|
||||||
|
@ -191,9 +193,9 @@ pub trait Format {
|
||||||
&self,
|
&self,
|
||||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||||
reader: impl Read + Send + Sync,
|
reader: impl Read + Send + Sync,
|
||||||
prompt: impl PromptHandler,
|
prompt: Box<dyn PromptHandler>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let prompt = Arc::new(Mutex::new(prompt));
|
let prompt = Rc::new(Mutex::new(prompt));
|
||||||
|
|
||||||
// parse input
|
// parse input
|
||||||
let private_keys = private_key_discovery
|
let private_keys = private_key_discovery
|
||||||
|
@ -233,6 +235,17 @@ pub trait Format {
|
||||||
let validator = MnemonicValidator {
|
let validator = MnemonicValidator {
|
||||||
word_length: Some(WordLength::Count(24)),
|
word_length: Some(WordLength::Count(24)),
|
||||||
};
|
};
|
||||||
|
let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX));
|
||||||
|
prompt_validated_wordlist::<English, _>(
|
||||||
|
&mut **prompt,
|
||||||
|
QRCODE_COULDNT_READ,
|
||||||
|
3,
|
||||||
|
&*validator.to_fn(),
|
||||||
|
)?
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| InvalidData)?
|
||||||
|
/*
|
||||||
prompt
|
prompt
|
||||||
.lock()
|
.lock()
|
||||||
.expect(bug!(POISONED_MUTEX))
|
.expect(bug!(POISONED_MUTEX))
|
||||||
|
@ -244,6 +257,7 @@ pub trait Format {
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| InvalidData)?
|
.map_err(|_| InvalidData)?
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -501,12 +515,12 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
word_lengths: [24, 39],
|
word_lengths: [24, 39],
|
||||||
};
|
};
|
||||||
|
|
||||||
let [pubkey_mnemonic, payload_mnemonic] = pm
|
let [pubkey_mnemonic, payload_mnemonic] = prompt_validated_wordlist::<English, _>(
|
||||||
.prompt_validated_wordlist::<English, _>(
|
&mut pm,
|
||||||
QRCODE_COULDNT_READ,
|
QRCODE_COULDNT_READ,
|
||||||
3,
|
3,
|
||||||
validator.to_fn(),
|
&*validator.to_fn(),
|
||||||
)?;
|
)?;
|
||||||
let pubkey = pubkey_mnemonic
|
let pubkey = pubkey_mnemonic
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.try_into()
|
.try_into()
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
marker::PhantomData,
|
|
||||||
path::Path,
|
path::Path,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, Mutex},
|
sync::Mutex,
|
||||||
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_bug::bug;
|
use keyfork_bug::bug;
|
||||||
|
@ -186,18 +186,9 @@ impl EncryptedMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encoding and decoding shards using OpenPGP.
|
/// Encoding and decoding shards using OpenPGP.
|
||||||
pub struct OpenPGP<P: PromptHandler> {
|
pub struct OpenPGP;
|
||||||
p: PhantomData<P>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: PromptHandler> OpenPGP<P> {
|
impl OpenPGP {
|
||||||
#[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.
|
/// 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.
|
/// Certificates are read from a file, or from files one level deep in a directory.
|
||||||
|
@ -256,7 +247,7 @@ impl<P: PromptHandler> OpenPGP<P> {
|
||||||
|
|
||||||
const METADATA_MESSAGE_MISSING: &str = "Metadata message was not found in parsed packets";
|
const METADATA_MESSAGE_MISSING: &str = "Metadata message was not found in parsed packets";
|
||||||
|
|
||||||
impl<P: PromptHandler> Format for OpenPGP<P> {
|
impl Format for OpenPGP {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type PublicKey = Cert;
|
type PublicKey = Cert;
|
||||||
type PrivateKeyData = Vec<Cert>;
|
type PrivateKeyData = Vec<Cert>;
|
||||||
|
@ -453,7 +444,7 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
|
||||||
&self,
|
&self,
|
||||||
private_keys: Option<Self::PrivateKeyData>,
|
private_keys: Option<Self::PrivateKeyData>,
|
||||||
encrypted_data: &[Self::EncryptedData],
|
encrypted_data: &[Self::EncryptedData],
|
||||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||||
) -> std::result::Result<(Vec<Share>, u8), Self::Error> {
|
) -> std::result::Result<(Vec<Share>, u8), Self::Error> {
|
||||||
// Be as liberal as possible when decrypting.
|
// Be as liberal as possible when decrypting.
|
||||||
// We don't want to invalidate someone's keys just because the old sig expired.
|
// We don't want to invalidate someone's keys just because the old sig expired.
|
||||||
|
@ -514,7 +505,7 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
|
||||||
&self,
|
&self,
|
||||||
private_keys: Option<Self::PrivateKeyData>,
|
private_keys: Option<Self::PrivateKeyData>,
|
||||||
encrypted_data: &[Self::EncryptedData],
|
encrypted_data: &[Self::EncryptedData],
|
||||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||||
) -> std::result::Result<(Share, u8), Self::Error> {
|
) -> std::result::Result<(Share, u8), Self::Error> {
|
||||||
let policy = NullPolicy::new();
|
let policy = NullPolicy::new();
|
||||||
|
|
||||||
|
@ -560,22 +551,22 @@ impl<P: PromptHandler> Format for OpenPGP<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> KeyDiscovery<OpenPGP<P>> for &Path {
|
impl KeyDiscovery<OpenPGP> for &Path {
|
||||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP<P> as Format>::PublicKey>> {
|
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
|
||||||
OpenPGP::<P>::discover_certs(self)
|
OpenPGP::discover_certs(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discover_private_keys(&self) -> Result<<OpenPGP<P> as Format>::PrivateKeyData> {
|
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
|
||||||
OpenPGP::<P>::discover_certs(self)
|
OpenPGP::discover_certs(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> KeyDiscovery<OpenPGP<P>> for &[Cert] {
|
impl KeyDiscovery<OpenPGP> for &[Cert] {
|
||||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP<P> as Format>::PublicKey>> {
|
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
|
||||||
Ok(self.to_vec())
|
Ok(self.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discover_private_keys(&self) -> Result<<OpenPGP<P> as Format>::PrivateKeyData> {
|
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
|
||||||
Ok(self.to_vec())
|
Ok(self.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -637,12 +628,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
|
// NOTE: When using single-decryptor mechanism, use this method with `threshold = 1` to return a
|
||||||
// single message.
|
// single message.
|
||||||
fn decrypt_with_manager<P: PromptHandler>(
|
fn decrypt_with_manager(
|
||||||
threshold: u8,
|
threshold: u8,
|
||||||
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
||||||
certs: &[Cert],
|
certs: &[Cert],
|
||||||
policy: &dyn Policy,
|
policy: &dyn Policy,
|
||||||
manager: &mut SmartcardManager<P>,
|
manager: &mut SmartcardManager,
|
||||||
) -> Result<HashMap<KeyID, Vec<u8>>> {
|
) -> Result<HashMap<KeyID, Vec<u8>>> {
|
||||||
let mut decrypted_messages = HashMap::new();
|
let mut decrypted_messages = HashMap::new();
|
||||||
|
|
||||||
|
@ -687,11 +678,11 @@ fn decrypt_with_manager<P: PromptHandler>(
|
||||||
|
|
||||||
// NOTE: When using single-decryptor mechanism, only a single key should be provided in Keyring to
|
// NOTE: When using single-decryptor mechanism, only a single key should be provided in Keyring to
|
||||||
// decrypt messages with.
|
// decrypt messages with.
|
||||||
fn decrypt_with_keyring<P: PromptHandler>(
|
fn decrypt_with_keyring(
|
||||||
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
||||||
certs: &[Cert],
|
certs: &[Cert],
|
||||||
policy: &NullPolicy,
|
policy: &NullPolicy,
|
||||||
keyring: &mut Keyring<P>,
|
keyring: &mut Keyring,
|
||||||
) -> Result<HashMap<KeyID, Vec<u8>>, Error> {
|
) -> Result<HashMap<KeyID, Vec<u8>>, Error> {
|
||||||
let mut decrypted_messages = HashMap::new();
|
let mut decrypted_messages = HashMap::new();
|
||||||
|
|
||||||
|
@ -721,11 +712,11 @@ fn decrypt_with_keyring<P: PromptHandler>(
|
||||||
Ok(decrypted_messages)
|
Ok(decrypted_messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_metadata<P: PromptHandler>(
|
fn decrypt_metadata(
|
||||||
message: &EncryptedMessage,
|
message: &EncryptedMessage,
|
||||||
policy: &NullPolicy,
|
policy: &NullPolicy,
|
||||||
keyring: &mut Keyring<P>,
|
keyring: &mut Keyring,
|
||||||
manager: &mut SmartcardManager<P>,
|
manager: &mut SmartcardManager,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
Ok(if keyring.is_empty() {
|
Ok(if keyring.is_empty() {
|
||||||
manager.load_any_card()?;
|
manager.load_any_card()?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![allow(clippy::expect_fun_call)]
|
#![allow(clippy::expect_fun_call)]
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{rc::Rc, sync::Mutex};
|
||||||
|
|
||||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||||
use keyfork_prompt::{Error as PromptError, PromptHandler};
|
use keyfork_prompt::{Error as PromptError, PromptHandler};
|
||||||
|
@ -27,14 +27,14 @@ pub enum Error {
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
pub struct Keyring<P: PromptHandler> {
|
pub struct Keyring {
|
||||||
full_certs: Vec<Cert>,
|
full_certs: Vec<Cert>,
|
||||||
root: Option<Cert>,
|
root: Option<Cert>,
|
||||||
pm: Arc<Mutex<P>>,
|
pm: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> Keyring<P> {
|
impl Keyring {
|
||||||
pub fn new(certs: impl AsRef<[Cert]>, p: Arc<Mutex<P>>) -> Result<Self> {
|
pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
full_certs: certs.as_ref().to_vec(),
|
full_certs: certs.as_ref().to_vec(),
|
||||||
root: Default::default(),
|
root: Default::default(),
|
||||||
|
@ -62,7 +62,7 @@ impl<P: PromptHandler> Keyring<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> VerificationHelper for &mut Keyring<P> {
|
impl VerificationHelper for &mut Keyring {
|
||||||
fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
||||||
Ok(ids
|
Ok(ids
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -108,7 +108,7 @@ impl<P: PromptHandler> VerificationHelper for &mut Keyring<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> DecryptionHelper for &mut Keyring<P> {
|
impl DecryptionHelper for &mut Keyring {
|
||||||
fn decrypt<D>(
|
fn decrypt<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pkesks: &[PKESK],
|
pkesks: &[PKESK],
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
sync::{Arc, Mutex},
|
rc::Rc,
|
||||||
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
|
prompt_validated_passphrase,
|
||||||
validators::{PinValidator, Validator},
|
validators::{PinValidator, Validator},
|
||||||
Error as PromptError, Message, PromptHandler,
|
Error as PromptError, Message, PromptHandler,
|
||||||
};
|
};
|
||||||
|
@ -71,15 +73,15 @@ fn format_name(input: impl AsRef<str>) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub struct SmartcardManager<P: PromptHandler> {
|
pub struct SmartcardManager {
|
||||||
current_card: Option<Card<Open>>,
|
current_card: Option<Card<Open>>,
|
||||||
root: Option<Cert>,
|
root: Option<Cert>,
|
||||||
pm: Arc<Mutex<P>>,
|
pm: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||||
pin_cache: HashMap<Fingerprint, String>,
|
pin_cache: HashMap<Fingerprint, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> SmartcardManager<P> {
|
impl SmartcardManager {
|
||||||
pub fn new(p: Arc<Mutex<P>>) -> Result<Self> {
|
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
current_card: None,
|
current_card: None,
|
||||||
root: None,
|
root: None,
|
||||||
|
@ -173,7 +175,7 @@ impl<P: PromptHandler> SmartcardManager<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> VerificationHelper for &mut SmartcardManager<P> {
|
impl VerificationHelper for &mut SmartcardManager {
|
||||||
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
||||||
#[allow(clippy::flat_map_option)]
|
#[allow(clippy::flat_map_option)]
|
||||||
Ok(ids
|
Ok(ids
|
||||||
|
@ -217,7 +219,7 @@ impl<P: PromptHandler> VerificationHelper for &mut SmartcardManager<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
|
impl DecryptionHelper for &mut SmartcardManager {
|
||||||
fn decrypt<D>(
|
fn decrypt<D>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pkesks: &[PKESK],
|
pkesks: &[PKESK],
|
||||||
|
@ -275,11 +277,8 @@ impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
|
||||||
} else {
|
} else {
|
||||||
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
|
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
|
||||||
};
|
};
|
||||||
let temp_pin = self
|
let mut prompt = self.pm.lock().expect(bug!(POISONED_MUTEX));
|
||||||
.pm
|
let temp_pin = prompt_validated_passphrase(&mut **prompt, &message, 3, &pin_validator)?;
|
||||||
.lock()
|
|
||||||
.expect(bug!(POISONED_MUTEX))
|
|
||||||
.prompt_validated_passphrase(&message, 3, &pin_validator)?;
|
|
||||||
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
|
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||||
match verification_status {
|
match verification_status {
|
||||||
#[allow(clippy::ignored_unit_patterns)]
|
#[allow(clippy::ignored_unit_patterns)]
|
||||||
|
|
|
@ -3,7 +3,13 @@ use clap::{Parser, Subcommand};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use keyfork_mnemonic::{English, Mnemonic};
|
use keyfork_mnemonic::{English, Mnemonic};
|
||||||
use keyfork_prompt::{default_terminal, DefaultTerminal};
|
use keyfork_prompt::{
|
||||||
|
default_handler, prompt_validated_wordlist,
|
||||||
|
validators::{
|
||||||
|
mnemonic::{MnemonicChoiceValidator, WordLength},
|
||||||
|
Validator,
|
||||||
|
},
|
||||||
|
};
|
||||||
use keyfork_shard::{remote_decrypt, Format};
|
use keyfork_shard::{remote_decrypt, Format};
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
@ -35,8 +41,8 @@ impl RecoverSubcommands {
|
||||||
} => {
|
} => {
|
||||||
let content = std::fs::read_to_string(shard_file)?;
|
let content = std::fs::read_to_string(shard_file)?;
|
||||||
if content.contains("BEGIN PGP MESSAGE") {
|
if content.contains("BEGIN PGP MESSAGE") {
|
||||||
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||||
let prompt_handler = default_terminal()?;
|
let prompt_handler = default_handler()?;
|
||||||
// TODO: remove .clone() by making handle() consume self
|
// TODO: remove .clone() by making handle() consume self
|
||||||
let seed = openpgp.decrypt_all_shards_to_secret(
|
let seed = openpgp.decrypt_all_shards_to_secret(
|
||||||
key_discovery.as_deref(),
|
key_discovery.as_deref(),
|
||||||
|
@ -54,21 +60,15 @@ impl RecoverSubcommands {
|
||||||
Ok(seed)
|
Ok(seed)
|
||||||
}
|
}
|
||||||
RecoverSubcommands::Mnemonic {} => {
|
RecoverSubcommands::Mnemonic {} => {
|
||||||
use keyfork_prompt::{
|
let mut prompt_handler = default_handler()?;
|
||||||
validators::{
|
|
||||||
mnemonic::{MnemonicChoiceValidator, WordLength},
|
|
||||||
Validator,
|
|
||||||
},
|
|
||||||
PromptHandler,
|
|
||||||
};
|
|
||||||
let mut term = default_terminal()?;
|
|
||||||
let validator = MnemonicChoiceValidator {
|
let validator = MnemonicChoiceValidator {
|
||||||
word_lengths: [WordLength::Count(12), WordLength::Count(24)],
|
word_lengths: [WordLength::Count(12), WordLength::Count(24)],
|
||||||
};
|
};
|
||||||
let mnemonic = term.prompt_validated_wordlist::<English, _>(
|
let mnemonic = prompt_validated_wordlist::<English, _>(
|
||||||
|
&mut *prompt_handler,
|
||||||
"Mnemonic: ",
|
"Mnemonic: ",
|
||||||
3,
|
3,
|
||||||
validator.to_fn(),
|
&*validator.to_fn(),
|
||||||
)?;
|
)?;
|
||||||
Ok(mnemonic.to_bytes())
|
Ok(mnemonic.to_bytes())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::Keyfork;
|
use super::Keyfork;
|
||||||
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
|
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
|
||||||
use keyfork_prompt::{default_terminal, DefaultTerminal};
|
use keyfork_prompt::default_handler;
|
||||||
use keyfork_shard::Format as _;
|
use keyfork_shard::Format as _;
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Read, Write},
|
io::{stdin, stdout, Read, Write},
|
||||||
|
@ -64,7 +64,7 @@ impl ShardExec for OpenPGP {
|
||||||
secret: &[u8],
|
secret: &[u8],
|
||||||
output: &mut (impl Write + Send + Sync),
|
output: &mut (impl Write + Send + Sync),
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let opgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
let opgp = keyfork_shard::openpgp::OpenPGP;
|
||||||
opgp.shard_and_encrypt(threshold, max, secret, key_discovery, output)
|
opgp.shard_and_encrypt(threshold, max, secret, key_discovery, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +74,8 @@ impl ShardExec for OpenPGP {
|
||||||
input: impl Read + Send + Sync,
|
input: impl Read + Send + Sync,
|
||||||
output: &mut impl Write,
|
output: &mut impl Write,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||||
let prompt = default_terminal()?;
|
let prompt = default_handler()?;
|
||||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input, prompt)?;
|
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input, prompt)?;
|
||||||
write!(output, "{}", smex::encode(bytes))?;
|
write!(output, "{}", smex::encode(bytes))?;
|
||||||
|
|
||||||
|
@ -87,8 +87,8 @@ impl ShardExec for OpenPGP {
|
||||||
key_discovery: Option<&Path>,
|
key_discovery: Option<&Path>,
|
||||||
input: impl Read + Send + Sync,
|
input: impl Read + Send + Sync,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||||
let prompt = default_terminal()?;
|
let prompt = default_handler()?;
|
||||||
openpgp.decrypt_one_shard_for_transport(key_discovery, input, prompt)?;
|
openpgp.decrypt_one_shard_for_transport(key_discovery, input, prompt)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,10 @@ use keyfork_derive_path_data::paths;
|
||||||
use keyfork_derive_util::DerivationIndex;
|
use keyfork_derive_util::DerivationIndex;
|
||||||
use keyfork_mnemonic::Mnemonic;
|
use keyfork_mnemonic::Mnemonic;
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
default_terminal,
|
default_handler,
|
||||||
|
prompt_validated_passphrase,
|
||||||
validators::{SecurePinValidator, Validator},
|
validators::{SecurePinValidator, Validator},
|
||||||
DefaultTerminal, Message, PromptHandler,
|
Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||||
|
@ -174,7 +175,7 @@ impl WizardSubcommands {
|
||||||
impl GenerateShardSecret {
|
impl GenerateShardSecret {
|
||||||
fn handle(&self) -> Result<()> {
|
fn handle(&self) -> Result<()> {
|
||||||
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||||
let mut pm = default_terminal()?;
|
let mut pm = default_handler()?;
|
||||||
let mut certs = vec![];
|
let mut certs = vec![];
|
||||||
let mut seen_cards: HashSet<String> = HashSet::new();
|
let mut seen_cards: HashSet<String> = HashSet::new();
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
|
@ -213,12 +214,14 @@ impl GenerateShardSecret {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))?;
|
))?;
|
||||||
};
|
};
|
||||||
let user_pin = pm.prompt_validated_passphrase(
|
let user_pin = prompt_validated_passphrase(
|
||||||
|
&mut *pm,
|
||||||
"Please enter the new smartcard User PIN: ",
|
"Please enter the new smartcard User PIN: ",
|
||||||
3,
|
3,
|
||||||
&user_pin_validator,
|
&user_pin_validator,
|
||||||
)?;
|
)?;
|
||||||
let admin_pin = pm.prompt_validated_passphrase(
|
let admin_pin = prompt_validated_passphrase(
|
||||||
|
&mut *pm,
|
||||||
"Please enter the new smartcard Admin PIN: ",
|
"Please enter the new smartcard Admin PIN: ",
|
||||||
3,
|
3,
|
||||||
&admin_pin_validator,
|
&admin_pin_validator,
|
||||||
|
@ -234,7 +237,7 @@ impl GenerateShardSecret {
|
||||||
certs.push(cert);
|
certs.push(cert);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
let opgp = OpenPGP;
|
||||||
|
|
||||||
if let Some(output_file) = self.output.as_ref() {
|
if let Some(output_file) = self.output.as_ref() {
|
||||||
let output = File::create(output_file)?;
|
let output = File::create(output_file)?;
|
||||||
|
@ -282,8 +285,8 @@ impl BottomsUp {
|
||||||
cert.serialize(&mut w)?;
|
cert.serialize(&mut w)?;
|
||||||
w.finalize()?;
|
w.finalize()?;
|
||||||
|
|
||||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
let opgp = OpenPGP;
|
||||||
let certs = OpenPGP::<DefaultTerminal>::discover_certs(&self.key_discovery)?;
|
let certs = OpenPGP::discover_certs(&self.key_discovery)?;
|
||||||
|
|
||||||
let shardfile = File::create(&self.output_shardfile)?;
|
let shardfile = File::create(&self.output_shardfile)?;
|
||||||
opgp.shard_and_encrypt(
|
opgp.shard_and_encrypt(
|
||||||
|
|
|
@ -1,38 +1,26 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use std::io::{stdin, stdout};
|
|
||||||
|
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
|
prompt_validated_wordlist,
|
||||||
validators::{mnemonic, Validator},
|
validators::{mnemonic, Validator},
|
||||||
Terminal, PromptHandler,
|
default_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_mnemonic::English;
|
use keyfork_mnemonic::English;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut mgr = Terminal::new(stdin(), stdout())?;
|
let mut handler = default_handler().unwrap();
|
||||||
let transport_validator = mnemonic::MnemonicSetValidator {
|
let transport_validator = mnemonic::MnemonicSetValidator {
|
||||||
word_lengths: [9, 24],
|
word_lengths: [24],
|
||||||
};
|
|
||||||
let combine_validator = mnemonic::MnemonicSetValidator {
|
|
||||||
word_lengths: [24, 48],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
|
let mnemonics = prompt_validated_wordlist::<English, _>(
|
||||||
"Enter a 9-word and 24-word mnemonic: ",
|
&mut *handler,
|
||||||
|
"Enter a 24-word mnemonic: ",
|
||||||
3,
|
3,
|
||||||
transport_validator.to_fn(),
|
&*transport_validator.to_fn(),
|
||||||
)?;
|
|
||||||
assert_eq!(mnemonics[0].as_bytes().len(), 12);
|
|
||||||
assert_eq!(mnemonics[1].as_bytes().len(), 32);
|
|
||||||
|
|
||||||
let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
|
|
||||||
"Enter a 24 and 48-word mnemonic: ",
|
|
||||||
3,
|
|
||||||
combine_validator.to_fn(),
|
|
||||||
)?;
|
)?;
|
||||||
assert_eq!(mnemonics[0].as_bytes().len(), 32);
|
assert_eq!(mnemonics[0].as_bytes().len(), 32);
|
||||||
assert_eq!(mnemonics[1].as_bytes().len(), 64);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
//! A headless prompt handler.
|
||||||
|
//!
|
||||||
|
//! This prompt handler uses the program's standard input and output to read inputs. It is not
|
||||||
|
//! directly intended to be machine-readable, but can be used for scriptable automation in a
|
||||||
|
//! fashion similar to a terminal handler.
|
||||||
|
|
||||||
|
use std::io::{IsTerminal, Write};
|
||||||
|
|
||||||
|
use crate::{BoxResult, Error, Message, PromptHandler, Result};
|
||||||
|
|
||||||
|
/// A headless prompt handler, usable in situations when a terminal might not be available, or for
|
||||||
|
/// scripting purposes where manual input from a terminal is not desirable.
|
||||||
|
pub struct Headless {
|
||||||
|
stdin: std::io::Stdin,
|
||||||
|
stderr: std::io::Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Headless {
|
||||||
|
/// Create a new [`Headless`] prompt handler.
|
||||||
|
#[allow(clippy::missing_errors_doc, clippy::new_without_default)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
stdin: std::io::stdin(),
|
||||||
|
stderr: std::io::stderr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptHandler for Headless {
|
||||||
|
fn prompt_input(&mut self, prompt: &str) -> Result<String> {
|
||||||
|
self.stderr.write_all(prompt.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
let mut line = String::new();
|
||||||
|
self.stdin.read_line(&mut line)?;
|
||||||
|
Ok(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_wordlist(&mut self, prompt: &str, _wordlist: &[&str]) -> Result<String> {
|
||||||
|
self.stderr.write_all(prompt.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
let mut line = String::new();
|
||||||
|
self.stdin.read_line(&mut line)?;
|
||||||
|
Ok(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
||||||
|
// Temporarily perform an IOCTL to disable printed output.
|
||||||
|
if self.stdin.is_terminal() {
|
||||||
|
eprintln!("WARNING: Headless terminal mode may leak passwords!");
|
||||||
|
}
|
||||||
|
self.stderr.write_all(prompt.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
let mut line = String::new();
|
||||||
|
self.stdin.read_line(&mut line)?;
|
||||||
|
Ok(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
|
||||||
|
match prompt {
|
||||||
|
Message::Text(s) => {
|
||||||
|
self.stderr.write_all(s.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
}
|
||||||
|
Message::Data(s) => {
|
||||||
|
self.stderr.write_all(s.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_validated_wordlist(
|
||||||
|
&mut self,
|
||||||
|
prompt: &str,
|
||||||
|
retries: u8,
|
||||||
|
_wordlist: &[&str],
|
||||||
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut line = String::new();
|
||||||
|
let mut last_error = String::new();
|
||||||
|
for _ in 0..retries {
|
||||||
|
self.stderr.write_all(prompt.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
self.stdin.read_line(&mut line)?;
|
||||||
|
if let Err(e) = validator_fn(std::mem::take(&mut line)) {
|
||||||
|
last_error = e.to_string();
|
||||||
|
self.stderr.write_all(e.to_string().as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::Validation(retries, last_error))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_validated_passphrase(
|
||||||
|
&mut self,
|
||||||
|
prompt: &str,
|
||||||
|
retries: u8,
|
||||||
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut line = String::new();
|
||||||
|
let mut last_error = String::new();
|
||||||
|
for _ in 0..retries {
|
||||||
|
self.stderr.write_all(prompt.as_bytes())?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
self.stdin.read_line(&mut line)?;
|
||||||
|
if let Err(e) = validator_fn(std::mem::take(&mut line)) {
|
||||||
|
last_error = e.to_string();
|
||||||
|
self.stderr.write_all(e.to_string().as_bytes())?;
|
||||||
|
self.stderr.write_all(b"\n")?;
|
||||||
|
self.stderr.flush()?;
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::Validation(retries, last_error))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,36 @@
|
||||||
//! Prompt display and interaction management.
|
//! Prompt display and interaction management.
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::io::IsTerminal;
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
#[cfg(feature = "mnemonic")]
|
||||||
use keyfork_mnemonic::Wordlist;
|
use keyfork_mnemonic::Wordlist;
|
||||||
|
|
||||||
///
|
pub mod headless;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
pub mod validators;
|
pub mod validators;
|
||||||
pub use terminal::{Terminal, DefaultTerminal, default_terminal};
|
pub use headless::Headless;
|
||||||
|
pub use terminal::{default_terminal, DefaultTerminal, Terminal};
|
||||||
|
|
||||||
|
/// An error occurred in the process of loading a default handler.
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum DefaultHandlerError {
|
||||||
|
/// An invalid handler was loaded.
|
||||||
|
#[error("An invalid handler was loaded: {handler} ({error})")]
|
||||||
|
InvalidHandler {
|
||||||
|
/// The handle that caused an error.
|
||||||
|
handler: String,
|
||||||
|
|
||||||
|
/// The error that occurred.
|
||||||
|
error: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An unknown handler was requested.
|
||||||
|
#[error("An unknown handler was requested: {handler}")]
|
||||||
|
UnknownHandler {
|
||||||
|
/// The requested, but unknown, handler.
|
||||||
|
handler: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// An error occurred while displaying a prompt.
|
/// An error occurred while displaying a prompt.
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -42,6 +64,9 @@ pub enum Message {
|
||||||
Data(String),
|
Data(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub type BoxResult = std::result::Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
/// A trait to allow displaying prompts and accepting input.
|
/// A trait to allow displaying prompts and accepting input.
|
||||||
pub trait PromptHandler {
|
pub trait PromptHandler {
|
||||||
/// Prompt the user for input.
|
/// Prompt the user for input.
|
||||||
|
@ -57,25 +82,7 @@ pub trait PromptHandler {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// The method may return an error if the message was not able to be displayed or if the input
|
/// The method may return an error if the message was not able to be displayed or if the input
|
||||||
/// could not be read.
|
/// could not be read.
|
||||||
#[cfg(feature = "mnemonic")]
|
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &[&str]) -> Result<String>;
|
||||||
fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String> where X: Wordlist;
|
|
||||||
|
|
||||||
/// Prompt the user for input based on a wordlist, while validating the wordlist using a
|
|
||||||
/// provided parser function, returning the type from the parser. A language must be specified
|
|
||||||
/// as the generic parameter `X` (any type implementing [`Wordlist`]) when parsing a wordlist.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// The method may return an error if the message was not able to be displayed, if the input
|
|
||||||
/// could not be read, or if the parser returned an error.
|
|
||||||
#[cfg(feature = "mnemonic")]
|
|
||||||
fn prompt_validated_wordlist<X, V>(
|
|
||||||
&mut self,
|
|
||||||
prompt: &str,
|
|
||||||
retries: u8,
|
|
||||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
|
||||||
) -> Result<V, Error>
|
|
||||||
where
|
|
||||||
X: Wordlist;
|
|
||||||
|
|
||||||
/// Prompt the user for a passphrase, which is hidden while typing.
|
/// Prompt the user for a passphrase, which is hidden while typing.
|
||||||
///
|
///
|
||||||
|
@ -84,23 +91,140 @@ pub trait PromptHandler {
|
||||||
/// could not be read.
|
/// could not be read.
|
||||||
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String>;
|
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String>;
|
||||||
|
|
||||||
/// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase
|
|
||||||
/// using a provided parser function, returning the type from the parser.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// The method may return an error if the message was not able to be displayed, if the input
|
|
||||||
/// could not be read, or if the parser returned an error.
|
|
||||||
fn prompt_validated_passphrase<V>(
|
|
||||||
&mut self,
|
|
||||||
prompt: &str,
|
|
||||||
retries: u8,
|
|
||||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
|
||||||
) -> Result<V, Error>;
|
|
||||||
|
|
||||||
/// Prompt the user with a [`Message`].
|
/// Prompt the user with a [`Message`].
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// The method may return an error if the message was not able to be displayed or if an error
|
/// The method may return an error if the message was not able to be displayed or if an error
|
||||||
/// occurred while waiting for the user to dismiss the message.
|
/// occurred while waiting for the user to dismiss the message.
|
||||||
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()>;
|
fn prompt_message(&mut self, prompt: Message) -> Result<()>;
|
||||||
|
|
||||||
|
/// Prompt the user for input based on a wordlist, while validating the wordlist using a
|
||||||
|
/// provided parser function, returning the type from the parser. A language must be specified
|
||||||
|
/// as the generic parameter `X` (any type implementing [`Wordlist`]) when parsing a wordlist.
|
||||||
|
///
|
||||||
|
/// This method MUST NOT be used directly. Instead, use
|
||||||
|
/// [`prompt_validated_wordlist`].
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// The method may return an error if the message was not able to be displayed, if the input
|
||||||
|
/// could not be read, or if the parser returned an error.
|
||||||
|
fn prompt_validated_wordlist(
|
||||||
|
&mut self,
|
||||||
|
prompt: &str,
|
||||||
|
retries: u8,
|
||||||
|
wordlist: &[&str],
|
||||||
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase
|
||||||
|
/// using a provided parser function, returning the type from the parser.
|
||||||
|
///
|
||||||
|
/// This method MUST NOT be used directly. Instead, use
|
||||||
|
/// [`prompt_validated_wordlist`].
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// The method may return an error if the message was not able to be displayed, if the input
|
||||||
|
/// could not be read, or if the parser returned an error.
|
||||||
|
fn prompt_validated_passphrase(
|
||||||
|
&mut self,
|
||||||
|
prompt: &str,
|
||||||
|
retries: u8,
|
||||||
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompt the user for input based on a wordlist, while validating the wordlist using a
|
||||||
|
/// provided parser function, returning the type from the parser. A language must be specified
|
||||||
|
/// as the generic parameter `X` (any type implementing [`Wordlist`]) when parsing a wordlist.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// The method may return an error if the message was not able to be displayed, if the input
|
||||||
|
/// could not be read, or if the parser returned an error.
|
||||||
|
#[cfg(feature = "mnemonic")]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
pub fn prompt_validated_wordlist<X, V>(
|
||||||
|
handler: &mut dyn PromptHandler,
|
||||||
|
prompt: &str,
|
||||||
|
retries: u8,
|
||||||
|
validator_fn: &dyn Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||||
|
) -> Result<V, Error>
|
||||||
|
where
|
||||||
|
X: Wordlist,
|
||||||
|
{
|
||||||
|
let wordlist = X::get_singleton();
|
||||||
|
let words = wordlist.to_str_array();
|
||||||
|
let mut opt: Option<V> = None;
|
||||||
|
handler.prompt_validated_wordlist(prompt, retries, &words, &mut |string| {
|
||||||
|
opt = Some(validator_fn(string)?);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(opt.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase
|
||||||
|
/// using a provided parser function, returning the type from the parser.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// The method may return an error if the message was not able to be displayed, if the input
|
||||||
|
/// could not be read, or if the parser returned an error.
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
pub fn prompt_validated_passphrase<V>(
|
||||||
|
handler: &mut dyn PromptHandler,
|
||||||
|
prompt: &str,
|
||||||
|
retries: u8,
|
||||||
|
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||||
|
) -> Result<V, Error> {
|
||||||
|
let mut opt: Option<V> = None;
|
||||||
|
handler.prompt_validated_passphrase(prompt, retries, &mut |string| {
|
||||||
|
opt = Some(validator_fn(string)?);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(opt.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a Prompt Handler that is most suitable for the given environment.
|
||||||
|
///
|
||||||
|
/// The following handlers will be used based on the `KEYFORK_PROMPT_TYPE` variable:
|
||||||
|
/// * `KEYFORK_PROMPT_TYPE=terminal`: [`DefaultTerminal`]
|
||||||
|
/// * `KEYFORK_PROMPT_TYPE=headless`: [`Headless`]
|
||||||
|
///
|
||||||
|
/// Otherwise, the following heuristics are followed:
|
||||||
|
/// * [`std::io::IsTerminal::is_terminal`]: [`DefaultTerminal`]
|
||||||
|
/// * default: [`Headless`]
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// The function will return an error if a specific handler was requested but could not be
|
||||||
|
/// constructed.
|
||||||
|
pub fn default_handler() -> Result<Box<dyn PromptHandler>, DefaultHandlerError> {
|
||||||
|
if let Some((_, value)) = std::env::vars().find(|(k, _)| k == "KEYFORK_PROMPT_TYPE") {
|
||||||
|
match value.as_str() {
|
||||||
|
"terminal" => match default_terminal() {
|
||||||
|
Ok(terminal) => return Ok(Box::new(terminal)),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(DefaultHandlerError::InvalidHandler {
|
||||||
|
handler: value,
|
||||||
|
error: e.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headless" => {
|
||||||
|
return Ok(Box::new(Headless::new()));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(DefaultHandlerError::UnknownHandler { handler: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdout can be not-a-terminal and we'll just override it anyways, stdin is the
|
||||||
|
// important one.
|
||||||
|
if std::io::stdin().is_terminal() {
|
||||||
|
// because this is a "guessed" handler, let's take the nice route and not error, just skip.
|
||||||
|
if let Ok(terminal) = default_terminal() {
|
||||||
|
return Ok(Box::new(terminal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Headless::new()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use keyfork_crossterm::{
|
||||||
|
|
||||||
use keyfork_bug::bug;
|
use keyfork_bug::bug;
|
||||||
|
|
||||||
use crate::{Error, Message, PromptHandler, Wordlist};
|
use crate::{BoxResult, Error, Message, PromptHandler};
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
@ -198,23 +198,20 @@ where
|
||||||
Ok(line)
|
Ok(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
fn prompt_validated_wordlist(
|
||||||
fn prompt_validated_wordlist<X, V>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
retries: u8,
|
retries: u8,
|
||||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
wordlist: &[&str],
|
||||||
) -> Result<V, Error>
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||||
where
|
) -> Result<(), Error> {
|
||||||
X: Wordlist,
|
|
||||||
{
|
|
||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
for _ in 0..retries {
|
for _ in 0..retries {
|
||||||
let s = self.prompt_wordlist::<X>(prompt)?;
|
let s = self.prompt_wordlist(prompt, wordlist)?;
|
||||||
match validator_fn(s) {
|
match validator_fn(s) {
|
||||||
Ok(v) => return Ok(v),
|
Ok(v) => return Ok(v),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.prompt_message(&Message::Text(format!("Error validating wordlist: {e}")))?;
|
self.prompt_message(Message::Text(format!("Error validating wordlist: {e}")))?;
|
||||||
let _ = last_error.insert(e);
|
let _ = last_error.insert(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,15 +224,8 @@ where
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String>
|
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &[&str]) -> Result<String> {
|
||||||
where
|
|
||||||
X: Wordlist,
|
|
||||||
{
|
|
||||||
let wordlist = X::get_singleton();
|
|
||||||
let words = wordlist.to_str_array();
|
|
||||||
|
|
||||||
let mut terminal = self
|
let mut terminal = self
|
||||||
.lock()
|
.lock()
|
||||||
.alternate_screen()?
|
.alternate_screen()?
|
||||||
|
@ -305,7 +295,7 @@ where
|
||||||
let word = input.split_whitespace().next_back().map(ToOwned::to_owned);
|
let word = input.split_whitespace().next_back().map(ToOwned::to_owned);
|
||||||
if let Some(steel_word) = word {
|
if let Some(steel_word) = word {
|
||||||
if steel_word.len() >= 4 {
|
if steel_word.len() >= 4 {
|
||||||
for word in words.iter().filter(|word| word.len() >= 4) {
|
for word in wordlist.iter().filter(|word| word.len() >= 4) {
|
||||||
if word[..4] == steel_word {
|
if word[..4] == steel_word {
|
||||||
input.push_str(&word[4..]);
|
input.push_str(&word[4..]);
|
||||||
input.push(' ');
|
input.push(' ');
|
||||||
|
@ -351,7 +341,7 @@ where
|
||||||
let mut iter = printable_input.split_whitespace().peekable();
|
let mut iter = printable_input.split_whitespace().peekable();
|
||||||
|
|
||||||
while let Some(word) = iter.next() {
|
while let Some(word) = iter.next() {
|
||||||
if words.contains(&word) {
|
if wordlist.contains(&word) {
|
||||||
terminal.queue(PrintStyledContent(word.green()))?;
|
terminal.queue(PrintStyledContent(word.green()))?;
|
||||||
} else {
|
} else {
|
||||||
terminal.queue(PrintStyledContent(word.red()))?;
|
terminal.queue(PrintStyledContent(word.red()))?;
|
||||||
|
@ -372,19 +362,19 @@ where
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt_validated_passphrase<V>(
|
fn prompt_validated_passphrase(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
retries: u8,
|
retries: u8,
|
||||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||||
) -> Result<V, Error> {
|
) -> Result<(), Error> {
|
||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
for _ in 0..retries {
|
for _ in 0..retries {
|
||||||
let s = self.prompt_passphrase(prompt)?;
|
let s = self.prompt_passphrase(prompt)?;
|
||||||
match validator_fn(s) {
|
match validator_fn(s) {
|
||||||
Ok(v) => return Ok(v),
|
Ok(v) => return Ok(v),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.prompt_message(&Message::Text(format!(
|
self.prompt_message(Message::Text(format!(
|
||||||
"Error validating passphrase: {e}"
|
"Error validating passphrase: {e}"
|
||||||
)))?;
|
)))?;
|
||||||
let _ = last_error.insert(e);
|
let _ = last_error.insert(e);
|
||||||
|
@ -461,7 +451,7 @@ where
|
||||||
Ok(passphrase)
|
Ok(passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()> {
|
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
|
||||||
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
Loading…
Reference in New Issue