Compare commits
3 Commits
aba62fc4bf
...
dd3ffe74b3
Author | SHA1 | Date |
---|---|---|
Ryan Heywood | dd3ffe74b3 | |
Ryan Heywood | b5320cabf3 | |
Ryan Heywood | 50cc58469d |
|
@ -42,6 +42,7 @@ pub(crate) fn fd_window_size(fd: i32) -> io::Result<WindowSize> {
|
||||||
ws_ypixel: 0,
|
ws_ypixel: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::useless_conversion)]
|
||||||
if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
|
if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
|
||||||
return Ok(size.into());
|
return Ok(size.into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,21 +1,11 @@
|
||||||
use std::{
|
use std::borrow::Borrow;
|
||||||
io::{stderr, stdin, BufRead, BufReader, Read, Stderr, Stdin, Write},
|
|
||||||
os::fd::AsRawFd,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
#[cfg(feature = "mnemonic")]
|
||||||
use keyfork_mnemonic_util::Wordlist;
|
use keyfork_mnemonic_util::Wordlist;
|
||||||
|
|
||||||
use keyfork_crossterm::{
|
pub mod terminal;
|
||||||
cursor,
|
|
||||||
event::{read, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
|
|
||||||
style::{Print, PrintStyledContent, Stylize},
|
|
||||||
terminal::{self, TerminalIoctl, FdTerminal, EnterAlternateScreen, LeaveAlternateScreen},
|
|
||||||
tty::IsTty,
|
|
||||||
QueueableCommand, ExecutableCommand
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod validators;
|
pub mod validators;
|
||||||
|
pub use terminal::{Terminal, DefaultTerminal, default_terminal};
|
||||||
|
|
||||||
#[cfg(feature = "qrencode")]
|
#[cfg(feature = "qrencode")]
|
||||||
pub mod qrencode;
|
pub mod qrencode;
|
||||||
|
@ -39,138 +29,13 @@ pub enum Message {
|
||||||
Data(String),
|
Data(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TerminalGuard<'a, R, W> where W: Write + AsRawFd {
|
pub trait PromptHandler {
|
||||||
read: &'a mut BufReader<R>,
|
fn prompt_input(&mut self, prompt: &str) -> Result<String>;
|
||||||
write: &'a mut W,
|
|
||||||
terminal: &'a mut FdTerminal,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, R, W> TerminalGuard<'a, R, W> where W: Write + AsRawFd, R: Read {
|
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String>;
|
||||||
fn new(read: &'a mut BufReader<R>, write: &'a mut W, terminal: &'a mut FdTerminal) -> Self {
|
|
||||||
Self {
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
terminal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alternate_screen(mut self) -> std::io::Result<Self> {
|
|
||||||
self.execute(EnterAlternateScreen)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn raw_mode(self) -> std::io::Result<Self> {
|
|
||||||
self.terminal.enable_raw_mode()?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bracketed_paste(mut self) -> std::io::Result<Self> {
|
|
||||||
self.execute(EnableBracketedPaste)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disable_raw_mode(&mut self) -> std::io::Result<()> {
|
|
||||||
self.terminal.disable_raw_mode()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> std::io::Result<(u16, u16)> {
|
|
||||||
self.terminal.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_size(&self) -> std::io::Result<terminal::WindowSize> {
|
|
||||||
self.terminal.window_size()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
|
|
||||||
self.read.fill_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consume(&mut self, amt: usize) {
|
|
||||||
self.read.consume(amt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
self.write.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
self.terminal.disable_raw_mode().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Terminal<R, W> {
|
|
||||||
read: BufReader<R>,
|
|
||||||
write: W,
|
|
||||||
terminal: FdTerminal,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R, W> Terminal<R, W>
|
|
||||||
where
|
|
||||||
R: Read + Sized,
|
|
||||||
W: Write + AsRawFd + Sized,
|
|
||||||
{
|
|
||||||
pub fn new(read_handle: R, write_handle: W) -> Result<Self> {
|
|
||||||
if !write_handle.is_tty() {
|
|
||||||
return Err(Error::NotATTY);
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
read: BufReader::new(read_handle),
|
|
||||||
terminal: FdTerminal::from(write_handle.as_raw_fd()),
|
|
||||||
write: write_handle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lock(&mut self) -> TerminalGuard<'_, R, W> {
|
|
||||||
TerminalGuard::new(&mut self.read, &mut self.write, &mut self.terminal)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_input(&mut self, prompt: &str) -> Result<String> {
|
|
||||||
let mut terminal = self.lock().alternate_screen()?;
|
|
||||||
terminal
|
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
|
||||||
.queue(cursor::MoveTo(0, 0))?;
|
|
||||||
let mut lines = prompt.lines().peekable();
|
|
||||||
while let Some(line) = lines.next() {
|
|
||||||
terminal.queue(Print(line))?;
|
|
||||||
if lines.peek().is_some() {
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
terminal.flush()?;
|
|
||||||
|
|
||||||
let mut line = String::new();
|
|
||||||
terminal.read.read_line(&mut line)?;
|
|
||||||
Ok(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
||||||
|
@ -179,147 +44,11 @@ where
|
||||||
) -> Result<V, Error>
|
) -> Result<V, Error>
|
||||||
where
|
where
|
||||||
F: Fn(String) -> Result<V, E>,
|
F: Fn(String) -> Result<V, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error;
|
||||||
{
|
|
||||||
let mut last_error = None;
|
|
||||||
for _ in 0..retries {
|
|
||||||
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}")))?;
|
|
||||||
let _ = last_error.insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::Validation(
|
|
||||||
retries,
|
|
||||||
last_error
|
|
||||||
.map(|e| e.to_string())
|
|
||||||
.unwrap_or_else(|| "Unknown".to_string()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String>;
|
||||||
#[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()?;
|
|
||||||
|
|
||||||
terminal
|
fn prompt_validated_passphrase<V, F, E>(
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
|
||||||
.queue(cursor::MoveTo(0, 0))?;
|
|
||||||
let mut lines = prompt.lines().peekable();
|
|
||||||
let mut prefix_length = 0;
|
|
||||||
while let Some(line) = lines.next() {
|
|
||||||
prefix_length = line.len();
|
|
||||||
terminal.queue(Print(line))?;
|
|
||||||
if lines.peek().is_some() {
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
terminal.flush()?;
|
|
||||||
|
|
||||||
let (mut cols, mut _rows) = terminal.size()?;
|
|
||||||
let mut input = String::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match read()? {
|
|
||||||
Event::Resize(new_cols, new_rows) => {
|
|
||||||
cols = new_cols;
|
|
||||||
_rows = new_rows;
|
|
||||||
}
|
|
||||||
Event::Paste(mut p) => {
|
|
||||||
p.retain(|c| c != '\n');
|
|
||||||
input.push_str(&p);
|
|
||||||
}
|
|
||||||
Event::Key(k) => match k.code {
|
|
||||||
KeyCode::Enter => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
input.pop();
|
|
||||||
}
|
|
||||||
KeyCode::Char('w') if k.modifiers.contains(KeyModifiers::CONTROL) => {
|
|
||||||
let mut has_deleted_text = true;
|
|
||||||
while input.pop().is_some_and(char::is_whitespace) {
|
|
||||||
has_deleted_text = false;
|
|
||||||
}
|
|
||||||
while input.pop().is_some_and(|c| !c.is_whitespace()) {
|
|
||||||
has_deleted_text = true;
|
|
||||||
}
|
|
||||||
if !input.is_empty() && has_deleted_text {
|
|
||||||
input.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(' ') => {
|
|
||||||
if !input.chars().next_back().is_some_and(char::is_whitespace) {
|
|
||||||
input.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
input.push(c);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
let usable_space = cols as usize - prefix_length - 1;
|
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveToColumn(
|
|
||||||
std::cmp::min(u16::MAX as usize, prefix_length) as u16,
|
|
||||||
))?
|
|
||||||
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
|
|
||||||
.flush()?;
|
|
||||||
|
|
||||||
let printable_input_start = if input.len() > usable_space {
|
|
||||||
let start_index = input.len() - usable_space;
|
|
||||||
// Find a word boundary, otherwise slice the current word in half
|
|
||||||
if let Some((index, _)) = input
|
|
||||||
.chars()
|
|
||||||
.enumerate()
|
|
||||||
.skip(start_index)
|
|
||||||
.find(|(_, ch)| ch.is_whitespace())
|
|
||||||
{
|
|
||||||
index
|
|
||||||
} else {
|
|
||||||
start_index
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let printable_input = &input[printable_input_start..];
|
|
||||||
let mut iter = printable_input.split_whitespace().peekable();
|
|
||||||
|
|
||||||
while let Some(word) = iter.next() {
|
|
||||||
if wordlist.contains(word) {
|
|
||||||
terminal.queue(PrintStyledContent(word.green()))?;
|
|
||||||
} else {
|
|
||||||
terminal.queue(PrintStyledContent(word.red()))?;
|
|
||||||
}
|
|
||||||
if iter.peek().is_some()
|
|
||||||
|| printable_input
|
|
||||||
.chars()
|
|
||||||
.next_back()
|
|
||||||
.is_some_and(char::is_whitespace)
|
|
||||||
{
|
|
||||||
terminal.queue(Print(" "))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.flush()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mnemonic")]
|
|
||||||
pub fn prompt_validated_passphrase<V, F, E>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
retries: u8,
|
retries: u8,
|
||||||
|
@ -327,160 +56,7 @@ where
|
||||||
) -> Result<V, Error>
|
) -> Result<V, Error>
|
||||||
where
|
where
|
||||||
F: Fn(String) -> Result<V, E>,
|
F: Fn(String) -> Result<V, E>,
|
||||||
E: std::error::Error,
|
E: std::error::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!("Error validating passphrase: {e}")))?;
|
|
||||||
let _ = last_error.insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::Validation(
|
|
||||||
retries,
|
|
||||||
last_error
|
|
||||||
.map(|e| e.to_string())
|
|
||||||
.unwrap_or_else(|| "Unknown".to_string()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: return secrecy::Secret<String>
|
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()>;
|
||||||
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
|
||||||
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
|
||||||
|
|
||||||
terminal
|
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
|
||||||
.queue(cursor::MoveTo(0, 0))?;
|
|
||||||
let mut lines = prompt.lines().peekable();
|
|
||||||
let mut prefix_length = 0;
|
|
||||||
while let Some(line) = lines.next() {
|
|
||||||
prefix_length = line.len();
|
|
||||||
terminal.queue(Print(line))?;
|
|
||||||
if lines.peek().is_some() {
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
terminal.flush()?;
|
|
||||||
|
|
||||||
let (mut cols, mut _rows) = terminal.size()?;
|
|
||||||
|
|
||||||
let mut passphrase = String::new();
|
|
||||||
loop {
|
|
||||||
match read()? {
|
|
||||||
Event::Resize(new_cols, new_rows) => {
|
|
||||||
cols = new_cols;
|
|
||||||
_rows = new_rows;
|
|
||||||
}
|
|
||||||
Event::Key(k) => match k.code {
|
|
||||||
KeyCode::Enter => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
let passphrase_len = passphrase.len();
|
|
||||||
if passphrase.pop().is_some()
|
|
||||||
&& prefix_length + passphrase_len < cols as usize
|
|
||||||
{
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveLeft(1))?
|
|
||||||
.queue(Print(" "))?
|
|
||||||
.queue(cursor::MoveLeft(1))?
|
|
||||||
.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if prefix_length + passphrase.len() < (cols - 1) as usize {
|
|
||||||
terminal.queue(Print("*"))?.flush()?;
|
|
||||||
}
|
|
||||||
passphrase.push(c);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(passphrase)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_message(&mut self, prompt: &Message) -> Result<()> {
|
|
||||||
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (cols, rows) = terminal.size()?;
|
|
||||||
|
|
||||||
terminal
|
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
|
||||||
.queue(cursor::MoveTo(0, 0))?;
|
|
||||||
|
|
||||||
match &prompt {
|
|
||||||
Message::Text(text) => {
|
|
||||||
for line in text.lines() {
|
|
||||||
let mut written_chars = 0;
|
|
||||||
for word in line.split_whitespace() {
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
let len = std::cmp::min(u16::MAX as usize, word.len()) as u16;
|
|
||||||
written_chars += len + 1;
|
|
||||||
if written_chars > cols {
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
written_chars = len + 1;
|
|
||||||
}
|
|
||||||
terminal.queue(Print(word))?.queue(Print(" "))?;
|
|
||||||
}
|
|
||||||
terminal
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::Data(data) => {
|
|
||||||
let count = data.lines().count();
|
|
||||||
// NOTE: GE to allow a MoveDown(1)
|
|
||||||
if count >= rows as usize {
|
|
||||||
let msg = format!(
|
|
||||||
"{} {count} {} {rows} {}",
|
|
||||||
"Could not print data", "lines long (screen is", "lines long)"
|
|
||||||
);
|
|
||||||
terminal
|
|
||||||
.queue(Print(msg))?
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
} else {
|
|
||||||
for line in data.lines() {
|
|
||||||
terminal
|
|
||||||
.queue(Print(line))?
|
|
||||||
.queue(cursor::MoveDown(1))?
|
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
terminal
|
|
||||||
.queue(cursor::DisableBlinking)?
|
|
||||||
.queue(PrintStyledContent(" OK ".negative()))?
|
|
||||||
.flush()?;
|
|
||||||
|
|
||||||
#[allow(clippy::single_match)]
|
|
||||||
match read()? {
|
|
||||||
Event::Key(k) => match k.code {
|
|
||||||
KeyCode::Enter | KeyCode::Char(' ' | 'q') => break,
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
terminal.queue(cursor::EnableBlinking)?.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DefaultTerminal = Terminal<Stdin, Stderr>;
|
|
||||||
|
|
||||||
pub fn default_terminal() -> Result<DefaultTerminal> {
|
|
||||||
Terminal::new(stdin(), stderr())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,497 @@
|
||||||
|
use std::{
|
||||||
|
io::{stderr, stdin, BufRead, BufReader, Read, Stderr, Stdin, Write},
|
||||||
|
os::fd::AsRawFd, borrow::Borrow,
|
||||||
|
};
|
||||||
|
|
||||||
|
use keyfork_crossterm::{
|
||||||
|
cursor,
|
||||||
|
event::{read, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
|
||||||
|
style::{Print, PrintStyledContent, Stylize},
|
||||||
|
terminal::{self, EnterAlternateScreen, FdTerminal, LeaveAlternateScreen, TerminalIoctl},
|
||||||
|
tty::IsTty,
|
||||||
|
ExecutableCommand, QueueableCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{PromptHandler, Message, Wordlist, Error};
|
||||||
|
|
||||||
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
fn new(read: &'a mut BufReader<R>, write: &'a mut W, terminal: &'a mut FdTerminal) -> Self {
|
||||||
|
Self {
|
||||||
|
read,
|
||||||
|
write,
|
||||||
|
terminal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alternate_screen(mut self) -> std::io::Result<Self> {
|
||||||
|
self.execute(EnterAlternateScreen)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_mode(self) -> std::io::Result<Self> {
|
||||||
|
self.terminal.enable_raw_mode()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bracketed_paste(mut self) -> std::io::Result<Self> {
|
||||||
|
self.execute(EnableBracketedPaste)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable_raw_mode(&mut self) -> std::io::Result<()> {
|
||||||
|
self.terminal.disable_raw_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> std::io::Result<(u16, u16)> {
|
||||||
|
self.terminal.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_size(&self) -> std::io::Result<terminal::WindowSize> {
|
||||||
|
self.terminal.window_size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
|
||||||
|
self.read.fill_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume(&mut self, amt: usize) {
|
||||||
|
self.read.consume(amt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.write.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
self.terminal.disable_raw_mode().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Terminal<R, W> {
|
||||||
|
read: BufReader<R>,
|
||||||
|
write: W,
|
||||||
|
terminal: FdTerminal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, W> Terminal<R, W>
|
||||||
|
where
|
||||||
|
R: Read + Sized,
|
||||||
|
W: Write + AsRawFd + Sized,
|
||||||
|
{
|
||||||
|
pub fn new(read_handle: R, write_handle: W) -> Result<Self> {
|
||||||
|
if !write_handle.is_tty() {
|
||||||
|
return Err(Error::NotATTY);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
read: BufReader::new(read_handle),
|
||||||
|
terminal: FdTerminal::from(write_handle.as_raw_fd()),
|
||||||
|
write: write_handle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lock(&mut self) -> TerminalGuard<'_, R, W> {
|
||||||
|
TerminalGuard::new(&mut self.read, &mut self.write, &mut self.terminal)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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))?
|
||||||
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
let mut lines = prompt.lines().peekable();
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
terminal.queue(Print(line))?;
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.flush()?;
|
||||||
|
|
||||||
|
let mut line = String::new();
|
||||||
|
terminal.read.read_line(&mut line)?;
|
||||||
|
Ok(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
{
|
||||||
|
let mut last_error = None;
|
||||||
|
for _ in 0..retries {
|
||||||
|
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}")))?;
|
||||||
|
let _ = last_error.insert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::Validation(
|
||||||
|
retries,
|
||||||
|
last_error
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.unwrap_or_else(|| "Unknown".to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mnemonic")]
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
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))?
|
||||||
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
let mut lines = prompt.lines().peekable();
|
||||||
|
let mut prefix_length = 0;
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
prefix_length = line.len();
|
||||||
|
terminal.queue(Print(line))?;
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.flush()?;
|
||||||
|
|
||||||
|
let (mut cols, mut _rows) = terminal.size()?;
|
||||||
|
let mut input = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match read()? {
|
||||||
|
Event::Resize(new_cols, new_rows) => {
|
||||||
|
cols = new_cols;
|
||||||
|
_rows = new_rows;
|
||||||
|
}
|
||||||
|
Event::Paste(mut p) => {
|
||||||
|
p.retain(|c| c != '\n');
|
||||||
|
input.push_str(&p);
|
||||||
|
}
|
||||||
|
Event::Key(k) => match k.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
input.pop();
|
||||||
|
}
|
||||||
|
KeyCode::Char('w') if k.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
let mut has_deleted_text = true;
|
||||||
|
while input.pop().is_some_and(char::is_whitespace) {
|
||||||
|
has_deleted_text = false;
|
||||||
|
}
|
||||||
|
while input.pop().is_some_and(|c| !c.is_whitespace()) {
|
||||||
|
has_deleted_text = true;
|
||||||
|
}
|
||||||
|
if !input.is_empty() && has_deleted_text {
|
||||||
|
input.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char(' ') => {
|
||||||
|
if !input.chars().next_back().is_some_and(char::is_whitespace) {
|
||||||
|
input.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
input.push(c);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let usable_space = cols as usize - prefix_length - 1;
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveToColumn(
|
||||||
|
std::cmp::min(u16::MAX as usize, prefix_length) as u16,
|
||||||
|
))?
|
||||||
|
.queue(terminal::Clear(terminal::ClearType::UntilNewLine))?
|
||||||
|
.flush()?;
|
||||||
|
|
||||||
|
let printable_input_start = if input.len() > usable_space {
|
||||||
|
let start_index = input.len() - usable_space;
|
||||||
|
// Find a word boundary, otherwise slice the current word in half
|
||||||
|
if let Some((index, _)) = input
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
.skip(start_index)
|
||||||
|
.find(|(_, ch)| ch.is_whitespace())
|
||||||
|
{
|
||||||
|
index
|
||||||
|
} else {
|
||||||
|
start_index
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let printable_input = &input[printable_input_start..];
|
||||||
|
let mut iter = printable_input.split_whitespace().peekable();
|
||||||
|
|
||||||
|
while let Some(word) = iter.next() {
|
||||||
|
if wordlist.contains(word) {
|
||||||
|
terminal.queue(PrintStyledContent(word.green()))?;
|
||||||
|
} else {
|
||||||
|
terminal.queue(PrintStyledContent(word.red()))?;
|
||||||
|
}
|
||||||
|
if iter.peek().is_some()
|
||||||
|
|| printable_input
|
||||||
|
.chars()
|
||||||
|
.next_back()
|
||||||
|
.is_some_and(char::is_whitespace)
|
||||||
|
{
|
||||||
|
terminal.queue(Print(" "))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.flush()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
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!(
|
||||||
|
"Error validating passphrase: {e}"
|
||||||
|
)))?;
|
||||||
|
let _ = last_error.insert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::Validation(
|
||||||
|
retries,
|
||||||
|
last_error
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.unwrap_or_else(|| "Unknown".to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: return secrecy::Secret<String>
|
||||||
|
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
||||||
|
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
||||||
|
|
||||||
|
terminal
|
||||||
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
let mut lines = prompt.lines().peekable();
|
||||||
|
let mut prefix_length = 0;
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
prefix_length = line.len();
|
||||||
|
terminal.queue(Print(line))?;
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.flush()?;
|
||||||
|
|
||||||
|
let (mut cols, mut _rows) = terminal.size()?;
|
||||||
|
|
||||||
|
let mut passphrase = String::new();
|
||||||
|
loop {
|
||||||
|
match read()? {
|
||||||
|
Event::Resize(new_cols, new_rows) => {
|
||||||
|
cols = new_cols;
|
||||||
|
_rows = new_rows;
|
||||||
|
}
|
||||||
|
Event::Key(k) => match k.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
let passphrase_len = passphrase.len();
|
||||||
|
if passphrase.pop().is_some()
|
||||||
|
&& prefix_length + passphrase_len < cols as usize
|
||||||
|
{
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveLeft(1))?
|
||||||
|
.queue(Print(" "))?
|
||||||
|
.queue(cursor::MoveLeft(1))?
|
||||||
|
.flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
if prefix_length + passphrase.len() < (cols - 1) as usize {
|
||||||
|
terminal.queue(Print("*"))?.flush()?;
|
||||||
|
}
|
||||||
|
passphrase.push(c);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()> {
|
||||||
|
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (cols, rows) = terminal.size()?;
|
||||||
|
|
||||||
|
terminal
|
||||||
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
|
||||||
|
match prompt.borrow() {
|
||||||
|
Message::Text(text) => {
|
||||||
|
for line in text.lines() {
|
||||||
|
let mut written_chars = 0;
|
||||||
|
for word in line.split_whitespace() {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let len = std::cmp::min(u16::MAX as usize, word.len()) as u16;
|
||||||
|
written_chars += len + 1;
|
||||||
|
if written_chars > cols {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
written_chars = len + 1;
|
||||||
|
}
|
||||||
|
terminal.queue(Print(word))?.queue(Print(" "))?;
|
||||||
|
}
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Data(data) => {
|
||||||
|
let count = data.lines().count();
|
||||||
|
// NOTE: GE to allow a MoveDown(1)
|
||||||
|
if count >= rows as usize {
|
||||||
|
let msg = format!(
|
||||||
|
"{} {count} {} {rows} {}",
|
||||||
|
"Could not print data", "lines long (screen is", "lines long)"
|
||||||
|
);
|
||||||
|
terminal
|
||||||
|
.queue(Print(msg))?
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
} else {
|
||||||
|
for line in data.lines() {
|
||||||
|
terminal
|
||||||
|
.queue(Print(line))?
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal
|
||||||
|
.queue(cursor::DisableBlinking)?
|
||||||
|
.queue(PrintStyledContent(" OK ".negative()))?
|
||||||
|
.flush()?;
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match read()? {
|
||||||
|
Event::Key(k) => match k.code {
|
||||||
|
KeyCode::Enter | KeyCode::Char(' ' | 'q') => break,
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.queue(cursor::EnableBlinking)?.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DefaultTerminal = Terminal<Stdin, Stderr>;
|
||||||
|
|
||||||
|
pub fn default_terminal() -> Result<DefaultTerminal> {
|
||||||
|
Terminal::new(stdin(), stderr())
|
||||||
|
}
|
|
@ -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