keyfork-prompt: traitify
This commit is contained in:
parent
50cc58469d
commit
b5320cabf3
|
@ -2,7 +2,7 @@ use std::io::{stdin, stdout};
|
||||||
|
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
validators::{mnemonic, Validator},
|
validators::{mnemonic, Validator},
|
||||||
Terminal,
|
Terminal, PromptHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{stderr, stdin, BufRead, BufReader, Read, Stderr, Stdin, Write},
|
io::{stderr, stdin, BufRead, BufReader, Read, Stderr, Stdin, Write},
|
||||||
os::fd::AsRawFd,
|
os::fd::AsRawFd, borrow::Borrow,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
#[cfg(feature = "mnemonic")]
|
||||||
|
@ -10,9 +10,9 @@ use keyfork_crossterm::{
|
||||||
cursor,
|
cursor,
|
||||||
event::{read, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
|
event::{read, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
|
||||||
style::{Print, PrintStyledContent, Stylize},
|
style::{Print, PrintStyledContent, Stylize},
|
||||||
terminal::{self, TerminalIoctl, FdTerminal, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{self, EnterAlternateScreen, FdTerminal, LeaveAlternateScreen, TerminalIoctl},
|
||||||
tty::IsTty,
|
tty::IsTty,
|
||||||
QueueableCommand, ExecutableCommand
|
ExecutableCommand, QueueableCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod validators;
|
pub mod validators;
|
||||||
|
@ -39,18 +39,57 @@ pub enum Message {
|
||||||
Data(String),
|
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>,
|
read: &'a mut BufReader<R>,
|
||||||
write: &'a mut W,
|
write: &'a mut W,
|
||||||
terminal: &'a mut FdTerminal,
|
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 {
|
fn new(read: &'a mut BufReader<R>, write: &'a mut W, terminal: &'a mut FdTerminal) -> Self {
|
||||||
Self {
|
Self {
|
||||||
read,
|
read,
|
||||||
write,
|
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<()> {
|
fn enable_raw_mode(&mut self) -> std::io::Result<()> {
|
||||||
self.terminal.enable_raw_mode()
|
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> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
self.read.read(buf)
|
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]> {
|
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
|
||||||
self.read.fill_buf()
|
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> {
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
self.write.write(buf)
|
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) {
|
fn drop(&mut self) {
|
||||||
self.write.execute(DisableBracketedPaste).unwrap();
|
self.write.execute(DisableBracketedPaste).unwrap();
|
||||||
self.write.execute(LeaveAlternateScreen).unwrap();
|
self.write.execute(LeaveAlternateScreen).unwrap();
|
||||||
|
@ -148,7 +205,11 @@ where
|
||||||
TerminalGuard::new(&mut self.read, &mut self.write, &mut self.terminal)
|
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()?;
|
let mut terminal = self.lock().alternate_screen()?;
|
||||||
terminal
|
terminal
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
|
@ -170,7 +231,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
#[cfg(feature = "mnemonic")]
|
||||||
pub fn prompt_validated_wordlist<V, F, E>(
|
fn prompt_validated_wordlist<V, F, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
wordlist: &Wordlist,
|
wordlist: &Wordlist,
|
||||||
|
@ -202,8 +263,12 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
#[cfg(feature = "mnemonic")]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String> {
|
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String> {
|
||||||
let mut terminal = self.lock().alternate_screen()?.raw_mode()?.bracketed_paste()?;
|
let mut terminal = self
|
||||||
|
.lock()
|
||||||
|
.alternate_screen()?
|
||||||
|
.raw_mode()?
|
||||||
|
.bracketed_paste()?;
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
|
@ -318,8 +383,7 @@ where
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
fn prompt_validated_passphrase<V, F, E>(
|
||||||
pub fn prompt_validated_passphrase<V, F, E>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
retries: u8,
|
retries: u8,
|
||||||
|
@ -335,7 +399,9 @@ where
|
||||||
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 passphrase: {e}")))?;
|
self.prompt_message(&Message::Text(format!(
|
||||||
|
"Error validating passphrase: {e}"
|
||||||
|
)))?;
|
||||||
let _ = last_error.insert(e);
|
let _ = last_error.insert(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,7 +415,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: return secrecy::Secret<String>
|
// 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()?;
|
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
|
@ -407,7 +473,7 @@ where
|
||||||
Ok(passphrase)
|
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()?;
|
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -417,7 +483,7 @@ where
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
.queue(cursor::MoveTo(0, 0))?;
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
|
||||||
match &prompt {
|
match prompt.borrow() {
|
||||||
Message::Text(text) => {
|
Message::Text(text) => {
|
||||||
for line in text.lines() {
|
for line in text.lines() {
|
||||||
let mut written_chars = 0;
|
let mut written_chars = 0;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use keyfork_mnemonic_util::{Mnemonic, Wordlist};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
qrencode,
|
qrencode,
|
||||||
validators::{mnemonic::MnemonicSetValidator, Validator},
|
validators::{mnemonic::MnemonicSetValidator, Validator},
|
||||||
Message as PromptMessage, Terminal,
|
Message as PromptMessage, Terminal, PromptHandler
|
||||||
};
|
};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use sharks::{Share, Sharks};
|
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 =
|
let key_mnemonic =
|
||||||
Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?;
|
Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?;
|
||||||
let combined_mnemonic = format!("{nonce_mnemonic} {key_mnemonic}");
|
let combined_mnemonic = format!("{nonce_mnemonic} {key_mnemonic}");
|
||||||
pm.prompt_message(&PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
"Our words: {combined_mnemonic}"
|
"Our words: {combined_mnemonic}"
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
if let Ok(qrcode) = qrencode::qrencode(&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 {
|
let validator = MnemonicSetValidator {
|
||||||
|
|
|
@ -19,7 +19,7 @@ use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationEr
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
qrencode,
|
qrencode,
|
||||||
validators::{mnemonic::MnemonicSetValidator, Validator},
|
validators::{mnemonic::MnemonicSetValidator, Validator},
|
||||||
Error as PromptError, Message as PromptMessage, Terminal,
|
Error as PromptError, Message as PromptMessage, Terminal, PromptHandler,
|
||||||
};
|
};
|
||||||
use openpgp::{
|
use openpgp::{
|
||||||
armor::{Kind, Writer},
|
armor::{Kind, Writer},
|
||||||
|
@ -476,12 +476,12 @@ pub fn decrypt(
|
||||||
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) };
|
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) };
|
||||||
let combined_mnemonic = format!("{our_mnemonic} {mnemonic}");
|
let combined_mnemonic = format!("{our_mnemonic} {mnemonic}");
|
||||||
|
|
||||||
pm.prompt_message(&PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
"Our words: {combined_mnemonic}"
|
"Our words: {combined_mnemonic}"
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
if let Ok(qrcode) = qrencode::qrencode(&combined_mnemonic) {
|
if let Ok(qrcode) = qrencode::qrencode(&combined_mnemonic) {
|
||||||
pm.prompt_message(&PromptMessage::Data(qrcode))?;
|
pm.prompt_message(PromptMessage::Data(qrcode))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -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::{
|
use super::openpgp::{
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
default_terminal,
|
default_terminal,
|
||||||
validators::{PinValidator, Validator},
|
validators::{PinValidator, Validator},
|
||||||
DefaultTerminal, Error as PromptError, Message,
|
DefaultTerminal, Error as PromptError, Message, PromptHandler
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::openpgp::{
|
use super::openpgp::{
|
||||||
|
@ -93,14 +93,14 @@ impl SmartcardManager {
|
||||||
/// Load any backend.
|
/// Load any backend.
|
||||||
pub fn load_any_card(&mut self) -> Result<Fingerprint> {
|
pub fn load_any_card(&mut self) -> Result<Fingerprint> {
|
||||||
let card_backend = loop {
|
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(),
|
"Please plug in a smart card and press enter".to_string(),
|
||||||
))?;
|
))?;
|
||||||
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
||||||
break c;
|
break c;
|
||||||
}
|
}
|
||||||
self.pm
|
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 mut card = Card::<Open>::new(card_backend).map_err(Error::OpenSmartCard)?;
|
||||||
let transaction = card.transaction().map_err(Error::Transaction)?;
|
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(),
|
"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.
|
// NOTE: This should not be hit, because of the above validator.
|
||||||
Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => {
|
Err(CardError::CardStatus(StatusBytes::IncorrectParametersCommandDataField)) => {
|
||||||
self.pm.prompt_message(&Message::Text(
|
self.pm.prompt_message(Message::Text(
|
||||||
"Invalid PIN length entered.".to_string(),
|
"Invalid PIN length entered.".to_string(),
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use keyfork_derive_util::{
|
||||||
};
|
};
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
validators::{PinValidator, Validator},
|
validators::{PinValidator, Validator},
|
||||||
Message, Terminal,
|
Message, Terminal, PromptHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[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 {
|
for index in 0..max {
|
||||||
let cert = derive_key(&seed, index)?;
|
let cert = derive_key(&seed, index)?;
|
||||||
for i in 0..keys_per_shard {
|
for i in 0..keys_per_shard {
|
||||||
pm.prompt_message(&Message::Text(format!(
|
pm.prompt_message(Message::Text(format!(
|
||||||
"Please insert key #{} for user #{}",
|
"Please insert key #{} for user #{}",
|
||||||
i + 1,
|
i + 1,
|
||||||
index + 1,
|
index + 1,
|
||||||
|
|
Loading…
Reference in New Issue