From 4c6c0715393bae1691fd09c82731db9db4342948 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jan 2024 14:46:31 -0500 Subject: [PATCH] keyfork: improve prompt UX of `wizard generate-shard-secret` --- crates/keyfork/src/cli/wizard.rs | 61 ++++++++++++++-------- crates/util/keyfork-prompt/src/lib.rs | 4 ++ crates/util/keyfork-prompt/src/terminal.rs | 9 ++++ 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/crates/keyfork/src/cli/wizard.rs b/crates/keyfork/src/cli/wizard.rs index a60e221..a405b2a 100644 --- a/crates/keyfork/src/cli/wizard.rs +++ b/crates/keyfork/src/cli/wizard.rs @@ -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,30 +78,31 @@ 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::::new(current_backend)?; - let mut transaction = card.transaction()?; - let application_identifier = transaction.application_identifier()?.ident(); - if seen_cards.contains(&application_identifier) { - // we were given the same card, error - panic!("Previously used card {application_identifier} was reused"); - } else { - seen_cards.insert(application_identifier); - } - transaction.factory_reset()?; - let mut admin = transaction.to_admin_card("12345678")?; - admin.upload_key(signing_key, KeyType::Signing, None)?; - admin.upload_key(decryption_key, KeyType::Decryption, None)?; - admin.upload_key(authentication_key, KeyType::Authentication, None)?; - transaction.change_user_pin("123456", user_pin)?; - transaction.change_admin_pin("12345678", admin_pin)?; + let mut card = Card::::new(card_backend)?; + let mut transaction = card.transaction()?; + let application_identifier = transaction.application_identifier()?.ident(); + if seen_cards.contains(&application_identifier) { + // we were given the same card, error + panic!("Previously used card {application_identifier} was reused"); } else { - panic!("No smart card found"); + seen_cards.insert(application_identifier); } + transaction.factory_reset()?; + let mut admin = transaction.to_admin_card("12345678")?; + admin.upload_key(signing_key, KeyType::Signing, None)?; + admin.upload_key(decryption_key, KeyType::Decryption, None)?; + admin.upload_key(authentication_key, KeyType::Authentication, None)?; + transaction.change_user_pin("123456", user_pin)?; + transaction.change_admin_pin("12345678", admin_pin)?; Ok(()) } -fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8, output_file: &Option) -> Result<()> { +fn generate_shard_secret( + threshold: u8, + max: u8, + keys_per_shard: u8, + output_file: &Option, +) -> 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); } diff --git a/crates/util/keyfork-prompt/src/lib.rs b/crates/util/keyfork-prompt/src/lib.rs index 72e8d9c..3169b96 100644 --- a/crates/util/keyfork-prompt/src/lib.rs +++ b/crates/util/keyfork-prompt/src/lib.rs @@ -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), diff --git a/crates/util/keyfork-prompt/src/terminal.rs b/crates/util/keyfork-prompt/src/terminal.rs index ae1d2c6..db7cb19 100644 --- a/crates/util/keyfork-prompt/src/terminal.rs +++ b/crates/util/keyfork-prompt/src/terminal.rs @@ -269,6 +269,9 @@ impl PromptHandler for Terminal 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 PromptHandler for Terminal 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 PromptHandler for Terminal 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, _ => (), },