keyfork: improve prompt UX of `wizard generate-shard-secret`

This commit is contained in:
Ryan Heywood 2024-01-18 14:46:31 -05:00
parent 240a10a063
commit 4c6c071539
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 52 additions and 22 deletions

View File

@ -1,6 +1,6 @@
use super::Keyfork;
use clap::{Parser, Subcommand};
use std::{collections::HashSet, io::IsTerminal, path::PathBuf, fs::OpenOptions};
use std::{collections::HashSet, fs::OpenOptions, io::IsTerminal, path::PathBuf};
use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
@ -56,6 +56,7 @@ fn factory_reset_current_card(
user_pin: &str,
admin_pin: &str,
cert: &Cert,
card_backend: PcscBackend,
) -> Result<()> {
let policy = openpgp::policy::NullPolicy::new();
let valid_cert = cert.with_policy(&policy, None)?;
@ -77,8 +78,7 @@ fn factory_reset_current_card(
.secret()
.next()
.expect("no authentication key found");
if let Some(current_backend) = PcscBackend::cards(None)?.next().transpose()? {
let mut card = Card::<Open>::new(current_backend)?;
let mut card = Card::<Open>::new(card_backend)?;
let mut transaction = card.transaction()?;
let application_identifier = transaction.application_identifier()?.ident();
if seen_cards.contains(&application_identifier) {
@ -94,13 +94,15 @@ fn factory_reset_current_card(
admin.upload_key(authentication_key, KeyType::Authentication, None)?;
transaction.change_user_pin("123456", user_pin)?;
transaction.change_admin_pin("12345678", admin_pin)?;
} else {
panic!("No smart card found");
}
Ok(())
}
fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8, output_file: &Option<PathBuf>) -> Result<()> {
fn generate_shard_secret(
threshold: u8,
max: u8,
keys_per_shard: u8,
output_file: &Option<PathBuf>,
) -> Result<()> {
let seed = keyfork_entropy::generate_entropy_of_size(256 / 8)?;
let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?;
let mut certs = vec![];
@ -128,10 +130,19 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8, output_file
let cert = derive_key(&seed, index)?;
for i in 0..keys_per_shard {
pm.prompt_message(Message::Text(format!(
"Please insert key #{} for user #{}",
"Please remove all keys and insert key #{} for user #{}",
i + 1,
index + 1,
)))?;
let card_backend = loop {
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
break c;
}
pm.prompt_message(Message::Text(
"No smart card was found. Please plug in a smart card and press enter"
.to_string(),
))?;
};
let user_pin = pm.prompt_validated_passphrase(
"Please enter the new smartcard User PIN: ",
3,
@ -142,7 +153,13 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8, output_file
3,
&admin_pin_validator,
)?;
factory_reset_current_card(&mut seen_cards, user_pin.trim(), admin_pin.trim(), &cert)?;
factory_reset_current_card(
&mut seen_cards,
user_pin.trim(),
admin_pin.trim(),
&cert,
card_backend,
)?;
}
certs.push(cert);
}

View File

@ -21,6 +21,10 @@ pub enum Error {
#[error("Validation of the input failed after {0} retries (last error: {1})")]
Validation(u8, String),
/// A ctrl-c interrupt was caught by the handler.
#[error("User pressed ctrl-c, terminating the session")]
CtrlC,
/// An error occurred while interacting with a terminal.
#[error("IO Error: {0}")]
IO(#[from] std::io::Error),

View File

@ -269,6 +269,9 @@ impl<R, W> PromptHandler for Terminal<R, W> where R: Read + Sized, W: Write + As
input.push(' ');
}
}
KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => {
return Err(Error::CtrlC);
}
KeyCode::Char(' ') => {
if !input.chars().next_back().is_some_and(char::is_whitespace) {
input.push(' ');
@ -410,6 +413,9 @@ impl<R, W> PromptHandler for Terminal<R, W> where R: Read + Sized, W: Write + As
.flush()?;
}
}
KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => {
return Err(Error::CtrlC);
}
KeyCode::Char(c) => {
if prefix_length + passphrase.len() < (cols - 1) as usize {
terminal.queue(Print("*"))?.flush()?;
@ -485,6 +491,9 @@ impl<R, W> PromptHandler for Terminal<R, W> where R: Read + Sized, W: Write + As
#[allow(clippy::single_match)]
match read()? {
Event::Key(k) => match k.code {
KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => {
return Err(Error::CtrlC);
}
KeyCode::Enter | KeyCode::Char(' ' | 'q') => break,
_ => (),
},