avl_auth/
crypto.rs

1//! Cryptographic utilities and key management
2
3use 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    /// Generate a cryptographically secure random token
16    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    /// Generate RSA key pair for JWT signing
23    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    /// Generate EC key pair for JWT signing
45    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    /// Hash data with SHA-256
65    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    /// Generate a secure password reset token
72    pub fn generate_reset_token(&self) -> String {
73        self.generate_token(32)
74    }
75
76    /// Generate email verification token
77    pub fn generate_verification_token(&self) -> String {
78        self.generate_token(32)
79    }
80
81    /// Constant-time string comparison
82    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    /// Encrypt sensitive data (AES-256-GCM)
88    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        // Prepend nonce to ciphertext
104        let mut result = nonce.to_vec();
105        result.append(&mut ciphertext);
106
107        Ok(result)
108    }
109
110    /// Decrypt sensitive data (AES-256-GCM)
111    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    /// Generate device fingerprint
134    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]; // 256-bit key
169        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}