use super::*;
use rand::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CorporatePolicy {
pub min_length: usize,
pub max_length: usize,
pub require_uppercase: bool,
pub require_lowercase: bool,
pub require_numbers: bool,
pub require_symbols: bool,
pub min_unique_chars: usize,
pub forbid_common_passwords: bool,
pub forbid_keyboard_patterns: bool,
}
impl Default for CorporatePolicy {
fn default() -> Self {
Self {
min_length: 12,
max_length: 128,
require_uppercase: true,
require_lowercase: true,
require_numbers: true,
require_symbols: true,
min_unique_chars: 8,
forbid_common_passwords: true,
forbid_keyboard_patterns: true,
}
}
}
impl PasswordPolicy for CorporatePolicy {
fn meets_requirements(&self, password: &str) -> bool {
let composition = self.analyze_composition(password);
if password.len() < self.min_length || password.len() > self.max_length {
return false;
}
if self.require_uppercase && !composition.has_uppercase {
return false;
}
if self.require_lowercase && !composition.has_lowercase {
return false;
}
if self.require_numbers && !composition.has_numbers {
return false;
}
if self.require_symbols && !composition.has_symbols {
return false;
}
if composition.unique_chars < self.min_unique_chars {
return false;
}
if self.forbid_common_passwords {
let password_lower = password.to_lowercase();
for common in COMMON_PASSWORDS {
if password_lower.contains(common) {
return false;
}
}
}
if self.forbid_keyboard_patterns {
let password_lower = password.to_lowercase();
for pattern in KEYBOARD_PATTERNS {
if password_lower.contains(pattern) {
return false;
}
}
}
true
}
fn generate(&self) -> String {
let mut rng = thread_rng();
let mut attempts = 0;
const MAX_ATTEMPTS: u32 = 1000;
while attempts < MAX_ATTEMPTS {
let password = self.generate_candidate(&mut rng);
if self.meets_requirements(&password) {
return password;
}
attempts += 1;
}
self.generate_guaranteed_compliant(&mut rng)
}
fn get_requirements(&self) -> String {
let mut requirements = Vec::new();
requirements.push(format!(
"Length: {}-{} characters",
self.min_length, self.max_length
));
if self.require_uppercase {
requirements.push("At least one uppercase letter".to_string());
}
if self.require_lowercase {
requirements.push("At least one lowercase letter".to_string());
}
if self.require_numbers {
requirements.push("At least one number".to_string());
}
if self.require_symbols {
requirements.push("At least one symbol".to_string());
}
requirements.push(format!(
"At least {} unique characters",
self.min_unique_chars
));
if self.forbid_common_passwords {
requirements.push("No common passwords or words".to_string());
}
if self.forbid_keyboard_patterns {
requirements.push("No keyboard patterns".to_string());
}
requirements.join("\n• ")
}
fn analyze_strength(&self, password: &str) -> PasswordAnalysis {
let composition = self.analyze_composition(password);
let mut score = 0u32;
let mut feedback = Vec::new();
match password.len() {
0..=7 => {
feedback.push("Password is too short".to_string());
}
8..=11 => {
score += 10;
feedback.push("Consider using a longer password".to_string());
}
12..=15 => score += 20,
16..=20 => score += 25,
_ => score += 30,
}
let mut char_types_used = 0;
if composition.has_lowercase {
score += 5;
char_types_used += 1;
} else {
feedback.push("Add lowercase letters".to_string());
}
if composition.has_uppercase {
score += 5;
char_types_used += 1;
} else {
feedback.push("Add uppercase letters".to_string());
}
if composition.has_numbers {
score += 5;
char_types_used += 1;
} else {
feedback.push("Add numbers".to_string());
}
if composition.has_symbols {
score += 10;
char_types_used += 1;
} else {
feedback.push("Add symbols for better security".to_string());
}
if char_types_used == 4 {
score += 10;
}
let uniqueness_ratio = composition.unique_chars as f64 / password.len() as f64;
score += (uniqueness_ratio * 20.0) as u32;
if composition.repeated_chars > password.len() / 3 {
score = score.saturating_sub(10);
feedback.push("Too many repeated characters".to_string());
}
let password_lower = password.to_lowercase();
let mut has_common_password = false;
for common in COMMON_PASSWORDS {
if password_lower.contains(common) {
score = score.saturating_sub(20);
feedback.push("Avoid common passwords and words".to_string());
has_common_password = true;
break;
}
}
let mut has_keyboard_pattern = false;
for pattern in KEYBOARD_PATTERNS {
if password_lower.contains(pattern) {
score = score.saturating_sub(15);
feedback.push("Avoid keyboard patterns".to_string());
has_keyboard_pattern = true;
break;
}
}
if password.len() > 20 && !has_common_password && !has_keyboard_pattern {
score += 15;
}
if password.len() > 30 && !has_common_password && !has_keyboard_pattern {
score += 10;
}
let charset_size = self.calculate_charset_size(&composition);
let entropy = (password.len() as f64) * (charset_size as f64).log2();
let strength = match score {
0..=25 => StrengthLevel::VeryWeak,
26..=45 => StrengthLevel::Weak,
46..=65 => StrengthLevel::Fair,
66..=80 => StrengthLevel::Good,
81..=95 => StrengthLevel::Strong,
_ => StrengthLevel::VeryStrong,
};
let time_to_crack = self.estimate_crack_time(entropy);
if feedback.is_empty() {
feedback.push("Excellent password!".to_string());
}
PasswordAnalysis {
strength,
score,
entropy,
time_to_crack,
feedback,
character_composition: composition,
}
}
}
impl CorporatePolicy {
fn analyze_composition(&self, password: &str) -> CharacterComposition {
let mut has_lowercase = false;
let mut has_uppercase = false;
let mut has_numbers = false;
let mut has_symbols = false;
let mut char_counts = HashMap::new();
for ch in password.chars() {
*char_counts.entry(ch).or_insert(0) += 1;
if ch.is_ascii_lowercase() {
has_lowercase = true;
} else if ch.is_ascii_uppercase() {
has_uppercase = true;
} else if ch.is_ascii_digit() {
has_numbers = true;
} else {
has_symbols = true;
}
}
let unique_chars = char_counts.len();
let repeated_chars = char_counts.values().filter(|&&count| count > 1).count();
CharacterComposition {
length: password.len(),
has_lowercase,
has_uppercase,
has_numbers,
has_symbols,
unique_chars,
repeated_chars,
}
}
fn generate_candidate(&self, rng: &mut ThreadRng) -> String {
let mut charset = String::new();
if self.require_lowercase {
charset.push_str(LOWERCASE);
}
if self.require_uppercase {
charset.push_str(UPPERCASE);
}
if self.require_numbers {
charset.push_str(NUMBERS);
}
if self.require_symbols {
charset.push_str(SAFE_SYMBOLS);
}
if charset.is_empty() {
charset = format!("{LOWERCASE}{UPPERCASE}{NUMBERS}{SAFE_SYMBOLS}");
}
let charset_chars: Vec<char> = charset.chars().collect();
let length = rng.gen_range(self.min_length..=self.max_length.min(32));
(0..length)
.map(|_| charset_chars[rng.gen_range(0..charset_chars.len())])
.collect()
}
fn generate_guaranteed_compliant(&self, rng: &mut ThreadRng) -> String {
let mut password = String::new();
if self.require_lowercase {
password.push(LOWERCASE.chars().choose(rng).unwrap());
}
if self.require_uppercase {
password.push(UPPERCASE.chars().choose(rng).unwrap());
}
if self.require_numbers {
password.push(NUMBERS.chars().choose(rng).unwrap());
}
if self.require_symbols {
password.push(SAFE_SYMBOLS.chars().choose(rng).unwrap());
}
let all_chars = format!("{LOWERCASE}{UPPERCASE}{NUMBERS}{SAFE_SYMBOLS}");
let all_chars: Vec<char> = all_chars.chars().collect();
while password.len() < self.min_length {
password.push(all_chars[rng.gen_range(0..all_chars.len())]);
}
let mut chars: Vec<char> = password.chars().collect();
chars.shuffle(rng);
chars.into_iter().collect()
}
fn calculate_charset_size(&self, composition: &CharacterComposition) -> usize {
let mut size = 0;
if composition.has_lowercase {
size += 26;
}
if composition.has_uppercase {
size += 26;
}
if composition.has_numbers {
size += 10;
}
if composition.has_symbols {
size += 32; }
size.max(1)
}
fn estimate_crack_time(&self, entropy: f64) -> String {
let guesses_per_second = 1_000_000_000.0;
let total_combinations = 2_f64.powf(entropy);
let seconds_to_crack = total_combinations / (2.0 * guesses_per_second);
if seconds_to_crack < 1.0 {
"Instantly".to_string()
} else if seconds_to_crack < 60.0 {
format!("{seconds_to_crack:.0} seconds")
} else if seconds_to_crack < 3600.0 {
format!("{:.0} minutes", seconds_to_crack / 60.0)
} else if seconds_to_crack < 86400.0 {
format!("{:.0} hours", seconds_to_crack / 3600.0)
} else if seconds_to_crack < 31536000.0 {
format!("{:.0} days", seconds_to_crack / 86400.0)
} else if seconds_to_crack < 31536000000.0 {
format!("{:.0} years", seconds_to_crack / 31536000.0)
} else {
"Centuries".to_string()
}
}
}
#[derive(Debug, Clone)]
pub struct HighSecurityPolicy {
pub min_length: usize,
pub require_all_char_types: bool,
pub min_entropy: f64,
}
impl Default for HighSecurityPolicy {
fn default() -> Self {
Self {
min_length: 16,
require_all_char_types: true,
min_entropy: 60.0,
}
}
}
impl PasswordPolicy for HighSecurityPolicy {
fn meets_requirements(&self, password: &str) -> bool {
if password.len() < self.min_length {
return false;
}
if self.require_all_char_types {
let has_lower = password.chars().any(|c| c.is_ascii_lowercase());
let has_upper = password.chars().any(|c| c.is_ascii_uppercase());
let has_digit = password.chars().any(|c| c.is_ascii_digit());
let has_symbol = password.chars().any(|c| !c.is_ascii_alphanumeric());
if !(has_lower && has_upper && has_digit && has_symbol) {
return false;
}
}
let charset_size = self.estimate_charset_size(password);
let entropy = (password.len() as f64) * (charset_size as f64).log2();
entropy >= self.min_entropy
}
fn generate(&self) -> String {
let mut rng = thread_rng();
loop {
let mut password = String::new();
password.push(LOWERCASE.chars().choose(&mut rng).unwrap());
password.push(UPPERCASE.chars().choose(&mut rng).unwrap());
password.push(NUMBERS.chars().choose(&mut rng).unwrap());
password.push(SYMBOLS.chars().choose(&mut rng).unwrap());
let all_chars = format!("{LOWERCASE}{UPPERCASE}{NUMBERS}{SAFE_SYMBOLS}");
let all_chars: Vec<char> = all_chars.chars().collect();
while password.len() < self.min_length {
password.push(all_chars[rng.gen_range(0..all_chars.len())]);
}
let mut chars: Vec<char> = password.chars().collect();
chars.shuffle(&mut rng);
let password: String = chars.into_iter().collect();
if self.meets_requirements(&password) {
return password;
}
}
}
fn get_requirements(&self) -> String {
format!(
"• Minimum length: {} characters\n\
• Must contain uppercase, lowercase, numbers, and symbols\n\
• Minimum entropy: {:.1} bits",
self.min_length, self.min_entropy
)
}
fn analyze_strength(&self, password: &str) -> PasswordAnalysis {
let corporate = CorporatePolicy::default();
let mut analysis = corporate.analyze_strength(password);
if password.len() >= self.min_length {
analysis.score += 10;
} else {
analysis.score = analysis.score.saturating_sub(20);
analysis
.feedback
.push("Password too short for high security".to_string());
}
if analysis.entropy >= self.min_entropy {
analysis.score += 15;
} else {
analysis.score = analysis.score.saturating_sub(15);
analysis
.feedback
.push("Insufficient entropy for high security".to_string());
}
analysis.strength = match analysis.score {
0..=30 => StrengthLevel::VeryWeak,
31..=50 => StrengthLevel::Weak,
51..=70 => StrengthLevel::Fair,
71..=85 => StrengthLevel::Good,
86..=95 => StrengthLevel::Strong,
_ => StrengthLevel::VeryStrong,
};
analysis
}
}
impl HighSecurityPolicy {
fn estimate_charset_size(&self, password: &str) -> usize {
let mut size = 0;
if password.chars().any(|c| c.is_ascii_lowercase()) {
size += 26;
}
if password.chars().any(|c| c.is_ascii_uppercase()) {
size += 26;
}
if password.chars().any(|c| c.is_ascii_digit()) {
size += 10;
}
if password.chars().any(|c| !c.is_ascii_alphanumeric()) {
size += 32;
}
size.max(1)
}
}
#[derive(Debug, Clone)]
pub enum PolicyType {
Corporate(CorporatePolicy),
HighSecurity(HighSecurityPolicy),
}
impl PolicyType {
pub fn meets_requirements(&self, password: &str) -> bool {
match self {
PolicyType::Corporate(policy) => policy.meets_requirements(password),
PolicyType::HighSecurity(policy) => policy.meets_requirements(password),
}
}
pub fn generate(&self) -> String {
match self {
PolicyType::Corporate(policy) => policy.generate(),
PolicyType::HighSecurity(policy) => policy.generate(),
}
}
pub fn get_requirements(&self) -> String {
match self {
PolicyType::Corporate(policy) => policy.get_requirements(),
PolicyType::HighSecurity(policy) => policy.get_requirements(),
}
}
pub fn analyze_strength(&self, password: &str) -> PasswordAnalysis {
match self {
PolicyType::Corporate(policy) => policy.analyze_strength(password),
PolicyType::HighSecurity(policy) => policy.analyze_strength(password),
}
}
}