keyfork-prompt: add SecurePinValidator for making new, secure, PINs
This commit is contained in:
parent
c0b19e2457
commit
5d2309e301
|
@ -11,7 +11,7 @@ use keyfork_derive_openpgp::{
|
|||
};
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyfork_prompt::{
|
||||
validators::{PinValidator, Validator},
|
||||
validators::{SecurePinValidator, Validator},
|
||||
Message, PromptHandler, DefaultTerminal, default_terminal
|
||||
};
|
||||
|
||||
|
@ -116,12 +116,12 @@ fn generate_shard_secret(
|
|||
);
|
||||
}
|
||||
|
||||
let user_pin_validator = PinValidator {
|
||||
let user_pin_validator = SecurePinValidator {
|
||||
min_length: Some(6),
|
||||
..Default::default()
|
||||
}
|
||||
.to_fn();
|
||||
let admin_pin_validator = PinValidator {
|
||||
let admin_pin_validator = SecurePinValidator {
|
||||
min_length: Some(8),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
|
@ -29,6 +29,84 @@ pub enum PinError {
|
|||
/// The PIN contained invalid characters.
|
||||
#[error("PIN contained invalid characters (found {0} at position {1})")]
|
||||
InvalidCharacters(char, usize),
|
||||
|
||||
/// The provided PIN had either too many repeated characters or too many sequential characters.
|
||||
#[error("PIN contained too many repeated or sequential characters")]
|
||||
InsecurePIN,
|
||||
}
|
||||
|
||||
/// Validate that a PIN is of a certain length, matches a range of characters, and does not use
|
||||
/// incrementing or decrementing sequences of characters.
|
||||
///
|
||||
/// The validator determines a score for a passphrase and, if the score is high enough, returns an
|
||||
/// error.
|
||||
///
|
||||
/// Score is calculated based on:
|
||||
/// * how many sequential characters are in the passphrase (ascending or descending)
|
||||
/// * how many repeated characters are in the passphrase
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SecurePinValidator {
|
||||
/// The minimum length of provided PINs.
|
||||
pub min_length: Option<usize>,
|
||||
|
||||
/// The maximum length of provided PINs.
|
||||
pub max_length: Option<usize>,
|
||||
|
||||
/// The characters allowed by the PIN parser.
|
||||
pub range: Option<RangeInclusive<char>>,
|
||||
|
||||
/// Whether repeated characters count against the PIN.
|
||||
pub ignore_repeated_characters: bool,
|
||||
|
||||
/// Whether sequential characters count against the PIN.
|
||||
pub ignore_sequential_characters: bool,
|
||||
}
|
||||
|
||||
impl Validator for SecurePinValidator {
|
||||
type Output = String;
|
||||
type Error = PinError;
|
||||
|
||||
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<String, Box<dyn std::error::Error>>> {
|
||||
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');
|
||||
let ignore_repeated_characters = self.ignore_repeated_characters;
|
||||
let ignore_sequential_characters = self.ignore_sequential_characters;
|
||||
Box::new(move |mut s: String| {
|
||||
s.truncate(s.trim_end().len());
|
||||
let len = s.len();
|
||||
if len < min_len {
|
||||
return Err(Box::new(PinError::TooShort(len, min_len)));
|
||||
}
|
||||
if len > max_len {
|
||||
return Err(Box::new(PinError::TooLong(len, max_len)));
|
||||
}
|
||||
let mut last_char = 0;
|
||||
let mut score = 0;
|
||||
for (index, ch) in s.chars().enumerate() {
|
||||
if !range.contains(&ch) {
|
||||
return Err(Box::new(PinError::InvalidCharacters(ch, index)));
|
||||
}
|
||||
if [-1, 1].contains(&(ch as i32 - last_char))
|
||||
&& !ignore_sequential_characters
|
||||
{
|
||||
score += 1;
|
||||
}
|
||||
last_char = ch as i32;
|
||||
}
|
||||
let mut chars = s.chars().collect::<Vec<_>>();
|
||||
chars.sort();
|
||||
chars.dedup();
|
||||
if !ignore_repeated_characters {
|
||||
// SAFETY: the amount of characters can't have _increased_ since deduping
|
||||
score += s.chars().count() - chars.len();
|
||||
}
|
||||
if score * 2 > s.chars().count() {
|
||||
return Err(Box::new(PinError::InsecurePIN))
|
||||
}
|
||||
Ok(s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that a PIN is of a certain length and matches a range of characters.
|
||||
|
@ -79,8 +157,8 @@ pub mod mnemonic {
|
|||
|
||||
use super::Validator;
|
||||
|
||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError};
|
||||
use keyfork_bug::bug;
|
||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError};
|
||||
|
||||
/// A mnemonic could not be validated from the given input.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
Loading…
Reference in New Issue