use totp_rs::{Algorithm, TOTP};
use qrcode::{QrCode, render::unicode};
use data_encoding::BASE32_NOPAD;
use rand::{thread_rng, Rng};
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Serialize, Deserialize};
use std::fs;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TOTPConfig {
pub secret: String,
pub issuer: String,
pub account: String,
pub algorithm: String,
pub digits: u32,
pub step: u64,
}
impl Default for TOTPConfig {
fn default() -> Self {
Self {
secret: String::new(),
issuer: "Sol SafeKey".to_string(),
account: "master-key".to_string(),
algorithm: "SHA1".to_string(),
digits: 6,
step: 30,
}
}
}
pub struct TOTPManager {
pub config: TOTPConfig,
}
impl TOTPManager {
pub fn new(config: TOTPConfig) -> Self {
Self { config }
}
pub fn generate_secret() -> String {
let mut secret = [0u8; 20]; thread_rng().fill(&mut secret);
BASE32_NOPAD.encode(&secret)
}
pub fn create_totp(&self) -> Result<TOTP, String> {
let algorithm = match self.config.algorithm.as_str() {
"SHA1" => Algorithm::SHA1,
"SHA256" => Algorithm::SHA256,
"SHA512" => Algorithm::SHA512,
_ => return Err("Unsupported algorithm".to_string()),
};
let secret_bytes = BASE32_NOPAD.decode(self.config.secret.as_bytes())
.map_err(|_| "Invalid secret format")?;
TOTP::new(
algorithm,
self.config.digits as usize,
1,
self.config.step,
secret_bytes,
).map_err(|e| format!("TOTP creation failed: {}", e))
}
pub fn generate_current_code(&self) -> Result<String, String> {
let totp = self.create_totp()?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
Ok(totp.generate(timestamp))
}
pub fn verify_code(&self, code: &str) -> Result<bool, String> {
let totp = self.create_totp()?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
for window in [-1, 0, 1] {
let check_time = timestamp as i64 + (window as i64 * self.config.step as i64);
if check_time > 0 {
let expected_code = totp.generate(check_time as u64);
if expected_code == code {
return Ok(true);
}
}
}
Ok(false)
}
pub fn verify_code_extended(&self, code: &str) -> Result<(bool, String), String> {
let totp = self.create_totp()?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let current_code = totp.generate(timestamp);
let mut debug_info = format!("当前时间戳: {}, 当前验证码: {}\n", timestamp, current_code);
for window in -1..=1 {
let check_time = timestamp as i64 + (window as i64 * self.config.step as i64);
if check_time > 0 {
let expected_code = totp.generate(check_time as u64);
debug_info.push_str(&format!("窗口 {}: 时间戳 {}, 验证码 {}\n", window, check_time, expected_code));
if expected_code == code {
return Ok((true, debug_info));
}
}
}
Ok((false, debug_info))
}
pub fn get_codes_for_windows(&self, windows: i32) -> Result<Vec<(i64, String)>, String> {
let totp = self.create_totp()?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut codes = Vec::new();
for window in -windows..=windows {
let check_time = timestamp as i64 + (window as i64 * self.config.step as i64);
if check_time > 0 {
let code = totp.generate(check_time as u64);
codes.push((check_time, code));
}
}
Ok(codes)
}
pub fn generate_qr_code(&self) -> Result<String, String> {
let uri = format!(
"otpauth://totp/{}:{}?secret={}&issuer={}&algorithm={}&digits={}&period={}",
self.config.issuer.replace(" ", "%20"),
self.config.account.replace(" ", "%20"),
self.config.secret,
self.config.issuer.replace(" ", "%20"),
self.config.algorithm,
self.config.digits,
self.config.step
);
let qr_code = QrCode::new(&uri)
.map_err(|e| format!("QR code generation failed: {}", e))?;
Ok(qr_code.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build())
}
pub fn get_manual_setup_info(&self) -> String {
format!(
"Account: {}\nIssuer: {}\nSecret Key: {}\nTime Step: {} seconds\nDigits: {}",
self.config.account,
self.config.issuer,
self.config.secret,
self.config.step,
self.config.digits
)
}
pub fn get_remaining_time(&self) -> u64 {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
self.config.step - (now % self.config.step)
}
pub fn generate_backup_codes(&self, count: usize) -> Vec<String> {
let mut codes = Vec::new();
let mut rng = thread_rng();
for _ in 0..count {
let code: String = (0..8)
.map(|_| rng.gen_range(0..=9).to_string())
.collect();
codes.push(format!("{}-{}", &code[0..4], &code[4..8]));
}
codes
}
}
pub fn save_totp_config(config: &TOTPConfig, file_path: &str) -> Result<(), String> {
let json_data = serde_json::to_string_pretty(config)
.map_err(|e| format!("JSON 序列化失败: {}", e))?;
fs::write(file_path, json_data)
.map_err(|e| format!("文件写入失败: {}", e))
}
pub fn load_totp_config(file_path: &str) -> Result<TOTPConfig, String> {
let content = fs::read_to_string(file_path)
.map_err(|e| format!("读取配置文件失败: {}", e))?;
serde_json::from_str(&content)
.map_err(|e| format!("配置文件解析失败: {}", e))
}
pub fn parse_encrypted_file(content: &str) -> Result<String, String> {
match serde_json::from_str::<serde_json::Value>(content) {
Ok(json) => {
json.get("encrypted_private_key")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.ok_or("文件中未找到加密私钥".to_string())
}
Err(_) => {
Ok(content.trim().to_string())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_secret_generation() {
let secret = TOTPManager::generate_secret();
assert!(!secret.is_empty());
assert!(secret.len() >= 32); }
#[test]
fn test_totp_creation() {
let secret = TOTPManager::generate_secret();
let config = TOTPConfig {
secret,
..Default::default()
};
let manager = TOTPManager::new(config);
assert!(manager.create_totp().is_ok());
}
#[test]
fn test_code_generation_and_verification() {
let secret = TOTPManager::generate_secret();
let config = TOTPConfig {
secret,
..Default::default()
};
let manager = TOTPManager::new(config);
let code = manager.generate_current_code().unwrap();
assert_eq!(code.len(), 6);
assert!(code.chars().all(|c| c.is_ascii_digit()));
assert!(manager.verify_code(&code).unwrap());
assert!(!manager.verify_code("000000").unwrap());
}
}