keyfork-prompt: make dyn Trait compatible in prep for allowing dynamic prompt handlers
This commit is contained in:
parent
d7bf3d16e1
commit
92dde3dcee
|
@ -11,10 +11,12 @@ use aes_gcm::{
|
|||
Aes256Gcm, KeyInit, Nonce,
|
||||
};
|
||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||
use blahaj::{Share, Sharks};
|
||||
use hkdf::Hkdf;
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_prompt::{
|
||||
prompt_validated_wordlist,
|
||||
validators::{
|
||||
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
||||
Validator,
|
||||
|
@ -22,7 +24,6 @@ use keyfork_prompt::{
|
|||
Message as PromptMessage, PromptHandler, Terminal,
|
||||
};
|
||||
use sha2::Sha256;
|
||||
use blahaj::{Share, Sharks};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
const PLAINTEXT_LENGTH: u8 = 32 // shard
|
||||
|
@ -233,6 +234,17 @@ pub trait Format {
|
|||
let validator = MnemonicValidator {
|
||||
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
|
||||
.lock()
|
||||
.expect(bug!(POISONED_MUTEX))
|
||||
|
@ -244,6 +256,7 @@ pub trait Format {
|
|||
.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| InvalidData)?
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -501,11 +514,11 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
word_lengths: [24, 39],
|
||||
};
|
||||
|
||||
let [pubkey_mnemonic, payload_mnemonic] = pm
|
||||
.prompt_validated_wordlist::<English, _>(
|
||||
let [pubkey_mnemonic, payload_mnemonic] = prompt_validated_wordlist::<English, _>(
|
||||
&mut pm,
|
||||
QRCODE_COULDNT_READ,
|
||||
3,
|
||||
validator.to_fn(),
|
||||
&*validator.to_fn(),
|
||||
)?;
|
||||
let pubkey = pubkey_mnemonic
|
||||
.as_bytes()
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::{
|
|||
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
use keyfork_prompt::{
|
||||
prompt_validated_passphrase,
|
||||
validators::{PinValidator, Validator},
|
||||
Error as PromptError, Message, PromptHandler,
|
||||
};
|
||||
|
@ -275,11 +276,8 @@ impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
|
|||
} else {
|
||||
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
|
||||
};
|
||||
let temp_pin = self
|
||||
.pm
|
||||
.lock()
|
||||
.expect(bug!(POISONED_MUTEX))
|
||||
.prompt_validated_passphrase(&message, 3, &pin_validator)?;
|
||||
let mut prompt = self.pm.lock().expect(bug!(POISONED_MUTEX));
|
||||
let temp_pin = prompt_validated_passphrase(&mut *prompt, &message, 3, &pin_validator)?;
|
||||
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||
match verification_status {
|
||||
#[allow(clippy::ignored_unit_patterns)]
|
||||
|
|
|
@ -3,7 +3,14 @@ use clap::{Parser, Subcommand};
|
|||
use std::path::PathBuf;
|
||||
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_prompt::{default_terminal, DefaultTerminal};
|
||||
use keyfork_prompt::{
|
||||
default_terminal, prompt_validated_wordlist,
|
||||
validators::{
|
||||
mnemonic::{MnemonicChoiceValidator, WordLength},
|
||||
Validator,
|
||||
},
|
||||
DefaultTerminal,
|
||||
};
|
||||
use keyfork_shard::{remote_decrypt, Format};
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
@ -54,21 +61,15 @@ impl RecoverSubcommands {
|
|||
Ok(seed)
|
||||
}
|
||||
RecoverSubcommands::Mnemonic {} => {
|
||||
use keyfork_prompt::{
|
||||
validators::{
|
||||
mnemonic::{MnemonicChoiceValidator, WordLength},
|
||||
Validator,
|
||||
},
|
||||
PromptHandler,
|
||||
};
|
||||
let mut term = default_terminal()?;
|
||||
let validator = MnemonicChoiceValidator {
|
||||
word_lengths: [WordLength::Count(12), WordLength::Count(24)],
|
||||
};
|
||||
let mnemonic = term.prompt_validated_wordlist::<English, _>(
|
||||
let mnemonic = prompt_validated_wordlist::<English, _>(
|
||||
&mut term,
|
||||
"Mnemonic: ",
|
||||
3,
|
||||
validator.to_fn(),
|
||||
&*validator.to_fn(),
|
||||
)?;
|
||||
Ok(mnemonic.to_bytes())
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use keyfork_derive_path_data::paths;
|
|||
use keyfork_derive_util::DerivationIndex;
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
use keyfork_prompt::{
|
||||
default_terminal,
|
||||
default_terminal, prompt_validated_passphrase,
|
||||
validators::{SecurePinValidator, Validator},
|
||||
DefaultTerminal, Message, PromptHandler,
|
||||
};
|
||||
|
@ -213,12 +213,14 @@ impl GenerateShardSecret {
|
|||
.to_string(),
|
||||
))?;
|
||||
};
|
||||
let user_pin = pm.prompt_validated_passphrase(
|
||||
let user_pin = prompt_validated_passphrase(
|
||||
&mut pm,
|
||||
"Please enter the new smartcard User PIN: ",
|
||||
3,
|
||||
&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: ",
|
||||
3,
|
||||
&admin_pin_validator,
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
use std::io::{stdin, stdout};
|
||||
|
||||
use keyfork_prompt::{
|
||||
prompt_validated_wordlist,
|
||||
validators::{mnemonic, Validator},
|
||||
Terminal, PromptHandler,
|
||||
Terminal,
|
||||
};
|
||||
|
||||
use keyfork_mnemonic::English;
|
||||
|
@ -18,18 +19,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
word_lengths: [24, 48],
|
||||
};
|
||||
|
||||
let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
|
||||
let mnemonics = prompt_validated_wordlist::<English, _>(
|
||||
&mut mgr,
|
||||
"Enter a 9-word and 24-word mnemonic: ",
|
||||
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, _>(
|
||||
let mnemonics = prompt_validated_wordlist::<English, _>(
|
||||
&mut mgr,
|
||||
"Enter a 24 and 48-word mnemonic: ",
|
||||
3,
|
||||
combine_validator.to_fn(),
|
||||
&*combine_validator.to_fn(),
|
||||
)?;
|
||||
assert_eq!(mnemonics[0].as_bytes().len(), 32);
|
||||
assert_eq!(mnemonics[1].as_bytes().len(), 64);
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
//! Prompt display and interaction management.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
use keyfork_mnemonic::Wordlist;
|
||||
|
||||
///
|
||||
pub mod terminal;
|
||||
pub mod validators;
|
||||
pub use terminal::{Terminal, DefaultTerminal, default_terminal};
|
||||
pub use terminal::{default_terminal, DefaultTerminal, Terminal};
|
||||
|
||||
/// An error occurred while displaying a prompt.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
@ -42,6 +40,9 @@ pub enum Message {
|
|||
Data(String),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub type BoxResult = std::result::Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
/// A trait to allow displaying prompts and accepting input.
|
||||
pub trait PromptHandler {
|
||||
/// Prompt the user for input.
|
||||
|
@ -57,25 +58,7 @@ pub trait PromptHandler {
|
|||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed or if the input
|
||||
/// could not be read.
|
||||
#[cfg(feature = "mnemonic")]
|
||||
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;
|
||||
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &[&str]) -> Result<String>;
|
||||
|
||||
/// Prompt the user for a passphrase, which is hidden while typing.
|
||||
///
|
||||
|
@ -84,23 +67,93 @@ pub trait PromptHandler {
|
|||
/// could not be read.
|
||||
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`].
|
||||
///
|
||||
/// # Errors
|
||||
/// 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.
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use keyfork_crossterm::{
|
|||
|
||||
use keyfork_bug::bug;
|
||||
|
||||
use crate::{Error, Message, PromptHandler, Wordlist};
|
||||
use crate::{BoxResult, Error, Message, PromptHandler};
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
@ -198,23 +198,20 @@ where
|
|||
Ok(line)
|
||||
}
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
fn prompt_validated_wordlist<X, V>(
|
||||
fn prompt_validated_wordlist(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error>
|
||||
where
|
||||
X: Wordlist,
|
||||
{
|
||||
wordlist: &[&str],
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<(), Error> {
|
||||
let mut last_error = None;
|
||||
for _ in 0..retries {
|
||||
let s = self.prompt_wordlist::<X>(prompt)?;
|
||||
let s = self.prompt_wordlist(prompt, wordlist)?;
|
||||
match validator_fn(s) {
|
||||
Ok(v) => return Ok(v),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -227,15 +224,8 @@ where
|
|||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String>
|
||||
where
|
||||
X: Wordlist,
|
||||
{
|
||||
let wordlist = X::get_singleton();
|
||||
let words = wordlist.to_str_array();
|
||||
|
||||
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &[&str]) -> Result<String> {
|
||||
let mut terminal = self
|
||||
.lock()
|
||||
.alternate_screen()?
|
||||
|
@ -305,7 +295,7 @@ where
|
|||
let word = input.split_whitespace().next_back().map(ToOwned::to_owned);
|
||||
if let Some(steel_word) = word {
|
||||
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 {
|
||||
input.push_str(&word[4..]);
|
||||
input.push(' ');
|
||||
|
@ -351,7 +341,7 @@ where
|
|||
let mut iter = printable_input.split_whitespace().peekable();
|
||||
|
||||
while let Some(word) = iter.next() {
|
||||
if words.contains(&word) {
|
||||
if wordlist.contains(&word) {
|
||||
terminal.queue(PrintStyledContent(word.green()))?;
|
||||
} else {
|
||||
terminal.queue(PrintStyledContent(word.red()))?;
|
||||
|
@ -372,19 +362,19 @@ where
|
|||
Ok(input)
|
||||
}
|
||||
|
||||
fn prompt_validated_passphrase<V>(
|
||||
fn prompt_validated_passphrase(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error> {
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<(), Error> {
|
||||
let mut last_error = None;
|
||||
for _ in 0..retries {
|
||||
let s = self.prompt_passphrase(prompt)?;
|
||||
match validator_fn(s) {
|
||||
Ok(v) => return Ok(v),
|
||||
Err(e) => {
|
||||
self.prompt_message(&Message::Text(format!(
|
||||
self.prompt_message(Message::Text(format!(
|
||||
"Error validating passphrase: {e}"
|
||||
)))?;
|
||||
let _ = last_error.insert(e);
|
||||
|
@ -461,7 +451,7 @@ where
|
|||
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()?;
|
||||
|
||||
loop {
|
||||
|
|
Loading…
Reference in New Issue