Compare commits
No commits in common. "1b33e3cf0e8e05e5e77d77f03d0dee24c3a9a87b" and "37d2f09c6b70d8f41c576f723b88be629a7b8e7d" have entirely different histories.
1b33e3cf0e
...
37d2f09c6b
|
@ -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)?;
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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(_) => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue