Compare commits

..

No commits in common. "1b33e3cf0e8e05e5e77d77f03d0dee24c3a9a87b" and "37d2f09c6b70d8f41c576f723b88be629a7b8e7d" have entirely different histories.

4 changed files with 28 additions and 192 deletions

View File

@ -20,8 +20,6 @@ mod raw_mode;
use alternate_screen::AlternateScreen; use alternate_screen::AlternateScreen;
use raw_mode::RawMode; use raw_mode::RawMode;
pub mod validators;
#[cfg(feature = "qrencode")] #[cfg(feature = "qrencode")]
pub mod qrencode; pub mod qrencode;
@ -30,9 +28,6 @@ pub enum Error {
#[error("The given handler is not a TTY")] #[error("The given handler is not a TTY")]
NotATTY, NotATTY,
#[error("Validation of the input failed after {0} retries (last error: {1})")]
Validation(u8, String),
#[error("IO Error: {0}")] #[error("IO Error: {0}")]
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
} }
@ -85,37 +80,6 @@ where
Ok(line) Ok(line)
} }
#[cfg(feature = "mnemonic")]
pub 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()),
))
}
// TODO: create a wrapper for bracketed paste similar to RawMode // TODO: create a wrapper for bracketed paste similar to RawMode
#[cfg(feature = "mnemonic")] #[cfg(feature = "mnemonic")]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
@ -240,36 +204,6 @@ where
Ok(input) Ok(input)
} }
#[cfg(feature = "mnemonic")]
pub 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> // TODO: return secrecy::Secret<String>
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> { pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?; let mut terminal = AlternateScreen::new(&mut self.write)?;

View File

@ -1,54 +0,0 @@
#![allow(clippy::type_complexity)]
pub trait Validator {
type Output;
type Error;
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Self::Output, Self::Error>>;
}
#[derive(thiserror::Error, Debug)]
pub enum PinError {
#[error("PIN too short: {0} < {1}")]
TooShort(usize, usize),
#[error("PIN too long: {0} > {1}")]
TooLong(usize, usize),
#[error("PIN contained invalid characters (found {0} at position {1})")]
InvalidCharacters(char, usize),
}
#[derive(Default, Clone)]
pub struct PinValidator {
pub min_length: Option<usize>,
pub max_length: Option<usize>,
pub range: Option<std::ops::RangeInclusive<char>>,
}
impl Validator for PinValidator {
type Output = String;
type Error = PinError;
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<String, PinError>> {
let min_len = self.min_length.unwrap_or(usize::MIN);
let max_len = self.max_length.unwrap_or(usize::MAX);
let range = self.range.clone().unwrap_or('0'..='9');
Box::new(move |mut s: String| {
s.truncate(s.trim_end().len());
let len = s.len();
if len < min_len {
return Err(PinError::TooShort(len, min_len));
}
if len > max_len {
return Err(PinError::TooLong(len, max_len));
}
for (index, ch) in s.chars().enumerate() {
if !range.contains(&ch) {
return Err(PinError::InvalidCharacters(ch, index));
}
}
Ok(s)
})
}
}

View File

@ -1,10 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use keyfork_prompt::{ use keyfork_prompt::{default_prompt_manager, DefaultPromptManager, Error as PromptError, Message};
default_prompt_manager,
validators::{PinValidator, Validator},
DefaultPromptManager, Error as PromptError, Message,
};
use super::openpgp::{ use super::openpgp::{
self, self,
@ -24,7 +20,7 @@ pub enum Error {
#[error("Smart card could not decrypt any matching PKESK packets")] #[error("Smart card could not decrypt any matching PKESK packets")]
SmartCardCouldNotDecrypt, SmartCardCouldNotDecrypt,
#[error("No smart card was found")] #[error("No smart card backend was stored")]
SmartCardNotFound, SmartCardNotFound,
#[error("Selected smart card has no decryption key")] #[error("Selected smart card has no decryption key")]
@ -92,18 +88,12 @@ 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 { PcscBackend::cards(None)?
self.pm.prompt_message(&Message::Text( .next()
"Please plug in a smart card and press enter".to_string(), .transpose()?
))?; .ok_or(Error::SmartCardNotFound)
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? { .and_then(|backend| {
break c; let mut card = Card::<Open>::new(backend).map_err(Error::OpenSmartCard)?;
}
self.pm.prompt_message(&Message::Text(
"No smart card was found".to_string(),
))?;
};
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)?;
let fingerprint = transaction let fingerprint = transaction
.fingerprints() .fingerprints()
@ -114,6 +104,7 @@ impl SmartcardManager {
drop(transaction); drop(transaction);
self.current_card.replace(card); self.current_card.replace(card);
Ok(fingerprint) Ok(fingerprint)
})
} }
/// Load a backend if any [`Fingerprint`] has been matched by a currently active card. /// Load a backend if any [`Fingerprint`] has been matched by a currently active card.
@ -155,9 +146,8 @@ impl SmartcardManager {
} }
} }
self.pm.prompt_message(&Message::Text( #[rustfmt::skip]
"Please plug in a smart card and press enter".to_string(), self.pm.prompt_message(&Message::Text("Please plug in a smart card and press enter".to_string()))?;
))?;
} }
Ok(None) Ok(None)
@ -232,11 +222,6 @@ impl DecryptionHelper for &mut SmartcardManager {
.context("Could not load application identifier")? .context("Could not load application identifier")?
.ident(); .ident();
let mut pin = self.pin_cache.get(&fp).cloned(); let mut pin = self.pin_cache.get(&fp).cloned();
let pin_validator = PinValidator {
min_length: Some(6),
..Default::default()
}
.to_fn();
while transaction while transaction
.pw_status_bytes() .pw_status_bytes()
.map_err(Error::PwStatusBytes)? .map_err(Error::PwStatusBytes)?
@ -255,9 +240,7 @@ impl DecryptionHelper for &mut SmartcardManager {
} else { } else {
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ") format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
}; };
let temp_pin = self let temp_pin = self.pm.prompt_passphrase(&message)?;
.pm
.prompt_validated_passphrase(&message, 3, &pin_validator)?;
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim()); let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
match verification_status { match verification_status {
#[allow(clippy::ignored_unit_patterns)] #[allow(clippy::ignored_unit_patterns)]
@ -265,11 +248,8 @@ impl DecryptionHelper for &mut SmartcardManager {
self.pin_cache.insert(fp.clone(), temp_pin.clone()); self.pin_cache.insert(fp.clone(), temp_pin.clone());
pin.replace(temp_pin); pin.replace(temp_pin);
} }
// 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(),
))?;
} }
Err(_) => {} Err(_) => {}
} }

View File

@ -10,14 +10,7 @@ use keyfork_derive_util::{
request::{DerivationAlgorithm, DerivationRequest}, request::{DerivationAlgorithm, DerivationRequest},
DerivationIndex, DerivationPath, DerivationIndex, DerivationPath,
}; };
use keyfork_prompt::{ use keyfork_prompt::{Message, PromptManager};
validators::{PinValidator, Validator},
Message, PromptManager,
};
#[derive(thiserror::Error, Debug)]
#[error("Invalid PIN length: {0}")]
pub struct PinLength(usize);
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>; type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -112,17 +105,6 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8) -> Result<(
"not printing shard to terminal, redirect output" "not printing shard to terminal, redirect output"
); );
let user_pin_validator = PinValidator {
min_length: Some(6),
..Default::default()
}
.to_fn();
let admin_pin_validator = PinValidator {
min_length: Some(8),
..Default::default()
}
.to_fn();
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 {
@ -131,16 +113,10 @@ fn generate_shard_secret(threshold: u8, max: u8, keys_per_shard: u8) -> Result<(
i + 1, i + 1,
index + 1, index + 1,
)))?; )))?;
let user_pin = pm.prompt_validated_passphrase( // TODO: add a second prompt for verification, perhaps as an argument to
"Please enter the new smartcard User PIN: ", // prompt_passphrase
3, let user_pin = pm.prompt_passphrase("Please enter the new smartcard User PIN: ")?;
&user_pin_validator, let admin_pin = pm.prompt_passphrase("Please enter the new smartcard Admin PIN: ")?;
)?;
let admin_pin = pm.prompt_validated_passphrase(
"Please enter the new smartcard Admin PIN: ",
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)?;
} }
certs.push(cert); certs.push(cert);