1use crate::error::{AuthError, Result};
4use rand::RngCore;
5use sha2::{Digest, Sha256};
6use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
7
8pub struct CryptoManager;
9
10impl CryptoManager {
11 pub fn new() -> Self {
12 Self
13 }
14
15 pub fn generate_token(&self, length: usize) -> String {
17 let mut bytes = vec![0u8; length];
18 rand::thread_rng().fill_bytes(&mut bytes);
19 URL_SAFE_NO_PAD.encode(&bytes)
20 }
21
22 pub fn generate_rsa_keypair(&self, bits: usize) -> Result<(String, String)> {
24 use rsa::{RsaPrivateKey, RsaPublicKey};
25 use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
26
27 let mut rng = rand::thread_rng();
28 let private_key = RsaPrivateKey::new(&mut rng, bits)
29 .map_err(|e| AuthError::CryptoError(e.to_string()))?;
30 let public_key = RsaPublicKey::from(&private_key);
31
32 let private_pem = private_key
33 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
34 .map_err(|e| AuthError::CryptoError(e.to_string()))?
35 .to_string();
36
37 let public_pem = public_key
38 .to_public_key_pem(rsa::pkcs8::LineEnding::LF)
39 .map_err(|e| AuthError::CryptoError(e.to_string()))?;
40
41 Ok((private_pem, public_pem))
42 }
43
44 pub fn generate_ec_keypair(&self) -> Result<(String, String)> {
46 use p256::SecretKey;
47 use p256::pkcs8::{EncodePrivateKey, EncodePublicKey};
48
49 let secret_key = SecretKey::random(&mut rand::thread_rng());
50 let public_key = secret_key.public_key();
51
52 let private_pem = secret_key
53 .to_pkcs8_pem(p256::pkcs8::LineEnding::LF)
54 .map_err(|e| AuthError::CryptoError(e.to_string()))?
55 .to_string();
56
57 let public_pem = public_key
58 .to_public_key_pem(p256::pkcs8::LineEnding::LF)
59 .map_err(|e| AuthError::CryptoError(e.to_string()))?;
60
61 Ok((private_pem, public_pem))
62 }
63
64 pub fn hash_sha256(&self, data: &[u8]) -> Vec<u8> {
66 let mut hasher = Sha256::new();
67 hasher.update(data);
68 hasher.finalize().to_vec()
69 }
70
71 pub fn generate_reset_token(&self) -> String {
73 self.generate_token(32)
74 }
75
76 pub fn generate_verification_token(&self) -> String {
78 self.generate_token(32)
79 }
80
81 pub fn constant_time_compare(&self, a: &str, b: &str) -> bool {
83 use subtle::ConstantTimeEq;
84 a.as_bytes().ct_eq(b.as_bytes()).into()
85 }
86
87 pub fn encrypt(&self, plaintext: &[u8], key: &[u8]) -> Result<Vec<u8>> {
89 use aes_gcm::{
90 aead::{Aead, AeadCore, KeyInit, OsRng},
91 Aes256Gcm,
92 };
93
94 let cipher = Aes256Gcm::new_from_slice(key)
95 .map_err(|e| AuthError::CryptoError(e.to_string()))?;
96
97 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
98
99 let mut ciphertext = cipher
100 .encrypt(&nonce, plaintext)
101 .map_err(|e| AuthError::CryptoError(e.to_string()))?;
102
103 let mut result = nonce.to_vec();
105 result.append(&mut ciphertext);
106
107 Ok(result)
108 }
109
110 pub fn decrypt(&self, ciphertext: &[u8], key: &[u8]) -> Result<Vec<u8>> {
112 use aes_gcm::{
113 aead::{Aead, KeyInit},
114 Aes256Gcm,
115 };
116 use aes_gcm::aead::generic_array::GenericArray;
117
118 if ciphertext.len() < 12 {
119 return Err(AuthError::CryptoError("Invalid ciphertext".to_string()));
120 }
121
122 let cipher = Aes256Gcm::new_from_slice(key)
123 .map_err(|e| AuthError::CryptoError(e.to_string()))?;
124
125 let (nonce_bytes, ciphertext_bytes) = ciphertext.split_at(12);
126 let nonce = GenericArray::from_slice(nonce_bytes);
127
128 cipher
129 .decrypt(nonce, ciphertext_bytes)
130 .map_err(|e| AuthError::CryptoError(e.to_string()))
131 }
132
133 pub fn generate_device_fingerprint(&self, user_agent: &str, ip: &str) -> String {
135 let data = format!("{}:{}", user_agent, ip);
136 let hash = self.hash_sha256(data.as_bytes());
137 URL_SAFE_NO_PAD.encode(&hash)
138 }
139}
140
141impl Default for CryptoManager {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_generate_token() {
153 let crypto = CryptoManager::new();
154 let token = crypto.generate_token(32);
155 assert!(!token.is_empty());
156 }
157
158 #[test]
159 fn test_constant_time_compare() {
160 let crypto = CryptoManager::new();
161 assert!(crypto.constant_time_compare("hello", "hello"));
162 assert!(!crypto.constant_time_compare("hello", "world"));
163 }
164
165 #[test]
166 fn test_encrypt_decrypt() {
167 let crypto = CryptoManager::new();
168 let key = &[0u8; 32]; let plaintext = b"secret data";
170
171 let ciphertext = crypto.encrypt(plaintext, key).unwrap();
172 let decrypted = crypto.decrypt(&ciphertext, key).unwrap();
173
174 assert_eq!(plaintext, &decrypted[..]);
175 }
176}