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 super::Keyfork;
use clap::{Parser, Subcommand}; 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 card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, types::KeyType, Card}; use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
@ -56,6 +56,7 @@ fn factory_reset_current_card(
user_pin: &str, user_pin: &str,
admin_pin: &str, admin_pin: &str,
cert: &Cert, cert: &Cert,
card_backend: PcscBackend,
) -> Result<()> { ) -> Result<()> {
let policy = openpgp::policy::NullPolicy::new(); let policy = openpgp::policy::NullPolicy::new();
let valid_cert = cert.with_policy(&policy, None)?; let valid_cert = cert.with_policy(&policy, None)?;
@ -77,30 +78,31 @@ fn factory_reset_current_card(
.secret() .secret()
.next() .next()
.expect("no authentication key found"); .expect("no authentication key found");
if let Some(current_backend) = PcscBackend::cards(None)?.next().transpose()? { let mut card = Card::<Open>::new(card_backend)?;
let mut card = Card::<Open>::new(current_backend)?; let mut transaction = card.transaction()?;
let mut transaction = card.transaction()?; let application_identifier = transaction.application_identifier()?.ident();
let application_identifier = transaction.application_identifier()?.ident(); if seen_cards.contains(&application_identifier) {
if seen_cards.contains(&application_identifier) { // we were given the same card, error
// we were given the same card, error panic!("Previously used card {application_identifier} was reused");
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)?;
} else { } 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(()) 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 seed = keyfork_entropy::generate_entropy_of_size(256 / 8)?;
let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?; let mut pm = Terminal::new(std::io::stdin(), std::io::stderr())?;
let mut certs = vec![]; 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)?; 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 remove all keys and insert key #{} for user #{}",
i + 1, i + 1,
index + 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( let user_pin = pm.prompt_validated_passphrase(
"Please enter the new smartcard User PIN: ", "Please enter the new smartcard User PIN: ",
3, 3,
@ -142,7 +153,13 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8, output_file
3, 3,
&admin_pin_validator, &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); certs.push(cert);
} }

View File

@ -21,6 +21,10 @@ pub enum Error {
#[error("Validation of the input failed after {0} retries (last error: {1})")] #[error("Validation of the input failed after {0} retries (last error: {1})")]
Validation(u8, String), 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. /// An error occurred while interacting with a terminal.
#[error("IO Error: {0}")] #[error("IO Error: {0}")]
IO(#[from] std::io::Error), 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(' '); input.push(' ');
} }
} }
KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => {
return Err(Error::CtrlC);
}
KeyCode::Char(' ') => { KeyCode::Char(' ') => {
if !input.chars().next_back().is_some_and(char::is_whitespace) { if !input.chars().next_back().is_some_and(char::is_whitespace) {
input.push(' '); input.push(' ');
@ -410,6 +413,9 @@ impl<R, W> PromptHandler for Terminal<R, W> where R: Read + Sized, W: Write + As
.flush()?; .flush()?;
} }
} }
KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => {
return Err(Error::CtrlC);
}
KeyCode::Char(c) => { KeyCode::Char(c) => {
if prefix_length + passphrase.len() < (cols - 1) as usize { if prefix_length + passphrase.len() < (cols - 1) as usize {
terminal.queue(Print("*"))?.flush()?; 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)] #[allow(clippy::single_match)]
match read()? { match read()? {
Event::Key(k) => match k.code { 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, KeyCode::Enter | KeyCode::Char(' ' | 'q') => break,
_ => (), _ => (),
}, },