keyfork-prompt: traitify

This commit is contained in:
Ryan Heywood 2024-01-10 23:28:56 -05:00
parent 50cc58469d
commit b5320cabf3
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
7 changed files with 102 additions and 36 deletions

View File

@ -2,7 +2,7 @@ use std::io::{stdin, stdout};
use keyfork_prompt::{
validators::{mnemonic, Validator},
Terminal,
Terminal, PromptHandler,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {

View File

@ -1,6 +1,6 @@
use std::{
io::{stderr, stdin, BufRead, BufReader, Read, Stderr, Stdin, Write},
os::fd::AsRawFd,
os::fd::AsRawFd, borrow::Borrow,
};
#[cfg(feature = "mnemonic")]
@ -10,9 +10,9 @@ use keyfork_crossterm::{
cursor,
event::{read, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
style::{Print, PrintStyledContent, Stylize},
terminal::{self, TerminalIoctl, FdTerminal, EnterAlternateScreen, LeaveAlternateScreen},
terminal::{self, EnterAlternateScreen, FdTerminal, LeaveAlternateScreen, TerminalIoctl},
tty::IsTty,
QueueableCommand, ExecutableCommand
ExecutableCommand, QueueableCommand,
};
pub mod validators;
@ -39,18 +39,57 @@ pub enum Message {
Data(String),
}
struct TerminalGuard<'a, R, W> where W: Write + AsRawFd {
pub trait PromptHandler {
fn prompt_input(&mut self, prompt: &str) -> Result<String>;
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String>;
#[cfg(feature = "mnemonic")]
fn prompt_validated_wordlist<V, F, E>(
&mut self,
prompt: &str,
wordlist: &Wordlist,
retries: u8,
validator_fn: F,
) -> Result<V, Error>
where
F: Fn(String) -> Result<V, E>,
E: std::error::Error;
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String>;
fn prompt_validated_passphrase<V, F, E>(
&mut self,
prompt: &str,
retries: u8,
validator_fn: F,
) -> Result<V, Error>
where
F: Fn(String) -> Result<V, E>,
E: std::error::Error;
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()>;
}
struct TerminalGuard<'a, R, W>
where
W: Write + AsRawFd,
{
read: &'a mut BufReader<R>,
write: &'a mut W,
terminal: &'a mut FdTerminal,
}
impl<'a, R, W> TerminalGuard<'a, R, W> where W: Write + AsRawFd, R: Read {
impl<'a, R, W> TerminalGuard<'a, R, W>
where
W: Write + AsRawFd,
R: Read,
{
fn new(read: &'a mut BufReader<R>, write: &'a mut W, terminal: &'a mut FdTerminal) -> Self {
Self {
read,
write,
terminal
terminal,
}
}
@ -70,7 +109,11 @@ impl<'a, R, W> TerminalGuard<'a, R, W> where W: Write + AsRawFd, R: Read {
}
}
impl<R, W> TerminalIoctl for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd {
impl<R, W> TerminalIoctl for TerminalGuard<'_, R, W>
where
R: Read,
W: Write + AsRawFd,
{
fn enable_raw_mode(&mut self) -> std::io::Result<()> {
self.terminal.enable_raw_mode()
}
@ -88,13 +131,21 @@ impl<R, W> TerminalIoctl for TerminalGuard<'_, R, W> where R: Read, W: Write + A
}
}
impl<R, W> Read for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd {
impl<R, W> Read for TerminalGuard<'_, R, W>
where
R: Read,
W: Write + AsRawFd,
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read.read(buf)
}
}
impl<R, W> BufRead for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd {
impl<R, W> BufRead for TerminalGuard<'_, R, W>
where
R: Read,
W: Write + AsRawFd,
{
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
self.read.fill_buf()
}
@ -104,7 +155,10 @@ impl<R, W> BufRead for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd
}
}
impl<R, W> Write for TerminalGuard<'_, R, W> where W: Write + AsRawFd {
impl<R, W> Write for TerminalGuard<'_, R, W>
where
W: Write + AsRawFd,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.write(buf)
}
@ -114,7 +168,10 @@ impl<R, W> Write for TerminalGuard<'_, R, W> where W: Write + AsRawFd {
}
}
impl<R, W> Drop for TerminalGuard<'_, R, W> where W: Write + AsRawFd {
impl<R, W> Drop for TerminalGuard<'_, R, W>
where
W: Write + AsRawFd,
{
fn drop(&mut self) {
self.write.execute(DisableBracketedPaste).unwrap();
self.write.execute(LeaveAlternateScreen).unwrap();
@ -148,7 +205,11 @@ where
TerminalGuard::new(&mut self.read, &mut self.write, &mut self.terminal)
}
pub fn prompt_input(&mut self, prompt: &str) -> Result<String> {
}
impl<R, W> PromptHandler for Terminal<R, W> where R: Read + Sized, W: Write + AsRawFd + Sized {
fn prompt_input(&mut self, prompt: &str) -> Result<String> {
let mut terminal = self.lock().alternate_screen()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
@ -170,7 +231,7 @@ where
}
#[cfg(feature = "mnemonic")]
pub fn prompt_validated_wordlist<V, F, E>(
fn prompt_validated_wordlist<V, F, E>(
&mut self,
prompt: &str,
wordlist: &Wordlist,
@ -202,8 +263,12 @@ where
#[cfg(feature = "mnemonic")]
#[allow(clippy::too_many_lines)]
pub fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String> {
let mut terminal = self.lock().alternate_screen()?.raw_mode()?.bracketed_paste()?;
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String> {
let mut terminal = self
.lock()
.alternate_screen()?
.raw_mode()?
.bracketed_paste()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
@ -318,8 +383,7 @@ where
Ok(input)
}
#[cfg(feature = "mnemonic")]
pub fn prompt_validated_passphrase<V, F, E>(
fn prompt_validated_passphrase<V, F, E>(
&mut self,
prompt: &str,
retries: u8,
@ -335,7 +399,9 @@ where
match validator_fn(s) {
Ok(v) => return Ok(v),
Err(e) => {
self.prompt_message(&Message::Text(format!("Error validating passphrase: {e}")))?;
self.prompt_message(&Message::Text(format!(
"Error validating passphrase: {e}"
)))?;
let _ = last_error.insert(e);
}
}
@ -349,7 +415,7 @@ where
}
// TODO: return secrecy::Secret<String>
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
terminal
@ -407,7 +473,7 @@ where
Ok(passphrase)
}
pub fn prompt_message(&mut self, prompt: &Message) -> Result<()> {
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()> {
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
loop {
@ -417,7 +483,7 @@ where
.queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::MoveTo(0, 0))?;
match &prompt {
match prompt.borrow() {
Message::Text(text) => {
for line in text.lines() {
let mut written_chars = 0;

View File

@ -9,7 +9,7 @@ use keyfork_mnemonic_util::{Mnemonic, Wordlist};
use keyfork_prompt::{
qrencode,
validators::{mnemonic::MnemonicSetValidator, Validator},
Message as PromptMessage, Terminal,
Message as PromptMessage, Terminal, PromptHandler
};
use sha2::Sha256;
use sharks::{Share, Sharks};
@ -59,12 +59,12 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
let key_mnemonic =
Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?;
let combined_mnemonic = format!("{nonce_mnemonic} {key_mnemonic}");
pm.prompt_message(&PromptMessage::Text(format!(
pm.prompt_message(PromptMessage::Text(format!(
"Our words: {combined_mnemonic}"
)))?;
if let Ok(qrcode) = qrencode::qrencode(&combined_mnemonic) {
pm.prompt_message(&PromptMessage::Data(qrcode))?;
pm.prompt_message(PromptMessage::Data(qrcode))?;
}
let validator = MnemonicSetValidator {

View File

@ -19,7 +19,7 @@ use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationEr
use keyfork_prompt::{
qrencode,
validators::{mnemonic::MnemonicSetValidator, Validator},
Error as PromptError, Message as PromptMessage, Terminal,
Error as PromptError, Message as PromptMessage, Terminal, PromptHandler,
};
use openpgp::{
armor::{Kind, Writer},
@ -476,12 +476,12 @@ pub fn decrypt(
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) };
let combined_mnemonic = format!("{our_mnemonic} {mnemonic}");
pm.prompt_message(&PromptMessage::Text(format!(
pm.prompt_message(PromptMessage::Text(format!(
"Our words: {combined_mnemonic}"
)))?;
if let Ok(qrcode) = qrencode::qrencode(&combined_mnemonic) {
pm.prompt_message(&PromptMessage::Data(qrcode))?;
pm.prompt_message(PromptMessage::Data(qrcode))?;
}
Ok(())

View File

@ -1,4 +1,4 @@
use keyfork_prompt::{Error as PromptError, DefaultTerminal, default_terminal};
use keyfork_prompt::{Error as PromptError, DefaultTerminal, default_terminal, PromptHandler};
use super::openpgp::{
self,

View File

@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use keyfork_prompt::{
default_terminal,
validators::{PinValidator, Validator},
DefaultTerminal, Error as PromptError, Message,
DefaultTerminal, Error as PromptError, Message, PromptHandler
};
use super::openpgp::{
@ -93,14 +93,14 @@ impl SmartcardManager {
/// Load any backend.
pub fn load_any_card(&mut self) -> Result<Fingerprint> {
let card_backend = loop {
self.pm.prompt_message(&Message::Text(
self.pm.prompt_message(Message::Text(
"Please plug in a smart card and press enter".to_string(),
))?;
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
break c;
}
self.pm
.prompt_message(&Message::Text("No smart card was found".to_string()))?;
.prompt_message(Message::Text("No smart card was found".to_string()))?;
};
let mut card = Card::<Open>::new(card_backend).map_err(Error::OpenSmartCard)?;
let transaction = card.transaction().map_err(Error::Transaction)?;
@ -154,7 +154,7 @@ impl SmartcardManager {
}
}
self.pm.prompt_message(&Message::Text(
self.pm.prompt_message(Message::Text(
"Please plug in a smart card and press enter".to_string(),
))?;
}
@ -266,7 +266,7 @@ impl DecryptionHelper for &mut SmartcardManager {
}
// NOTE: This should not be hit, because of the above validator.
Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => {
self.pm.prompt_message(&Message::Text(
self.pm.prompt_message(Message::Text(
"Invalid PIN length entered.".to_string(),
))?;
}

View File

@ -12,7 +12,7 @@ use keyfork_derive_util::{
};
use keyfork_prompt::{
validators::{PinValidator, Validator},
Message, Terminal,
Message, Terminal, PromptHandler,
};
#[derive(thiserror::Error, Debug)]
@ -125,7 +125,7 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8) -> Result<(
for index in 0..max {
let cert = derive_key(&seed, index)?;
for i in 0..keys_per_shard {
pm.prompt_message(&Message::Text(format!(
pm.prompt_message(Message::Text(format!(
"Please insert key #{} for user #{}",
i + 1,
index + 1,