rust_keyvault/
crypto.rs

1//! Core cryptographic traits and primitives
2
3use crate::{key::SecretKey, Algorithm, Error, Result};
4use aead::{generic_array::typenum::U12, Aead, KeyInit, Payload};
5use aes_gcm::Aes256Gcm;
6use chacha20poly1305::{
7    ChaCha20Poly1305, Key as ChaChaKey, Nonce as ChaChaNonce, XChaCha20Poly1305, XNonce,
8};
9use rand_core::{CryptoRng, RngCore};
10
11// Explicit concrete type aliases with explicit sizes
12type AesKey = aes_gcm::Key<Aes256Gcm>;
13type AesNonce = aes_gcm::Nonce<U12>;
14
15/// Trait for random number generators suitable for cryptographic use
16pub trait SecureRandom: RngCore + CryptoRng {
17    /// Fill a buffer with cryptographically secure random bytes
18    fn fill_secure_bytes(&mut self, dest: &mut [u8]) -> Result<()> {
19        self.try_fill_bytes(dest).map_err(|e| {
20            Error::crypto("fill_bytes", &format!("failed to fill secure bytes: {}", e))
21        })?;
22        Ok(())
23    }
24}
25
26/// Trait for key generation
27pub trait KeyGenerator {
28    /// The type of key this generator produces
29    type Key;
30
31    /// Generate a new key
32    fn generate<R: SecureRandom>(&self, rng: &mut R) -> Result<Self::Key>;
33
34    /// Generate a new key with specific parameters
35    fn generate_with_params<R: SecureRandom>(
36        &self,
37        rng: &mut R,
38        params: KeyGenParams,
39    ) -> Result<Self::Key>;
40}
41/// A simple symmetric key generator implementation
42pub struct SimpleSymmetricKeyGenerator;
43
44impl KeyGenerator for SimpleSymmetricKeyGenerator {
45    type Key = SecretKey;
46
47    fn generate<R: SecureRandom>(&self, rng: &mut R) -> Result<Self::Key> {
48        let algorithm = Algorithm::ChaCha20Poly1305; // default algorithm
49        let key_len = algorithm.key_size();
50        let mut buf = vec![0u8; key_len];
51        rng.fill_secure_bytes(&mut buf)?;
52        SecretKey::from_bytes(buf, algorithm)
53    }
54
55    fn generate_with_params<R: SecureRandom>(
56        &self,
57        rng: &mut R,
58        params: KeyGenParams,
59    ) -> Result<Self::Key> {
60        let algorithm = params.algorithm;
61        let key_len = params.key_size.unwrap_or(algorithm.key_size());
62        let mut buf = vec![0u8; key_len];
63        rng.fill_secure_bytes(&mut buf)?;
64        SecretKey::from_bytes(buf, algorithm)
65    }
66}
67
68/// Parameters for key generation
69#[derive(Debug, Clone)]
70pub struct KeyGenParams {
71    /// Algorithm to generate the key for
72    pub algorithm: Algorithm,
73    /// Optional seed materials (for deterministic generation)
74    pub seed: Option<Vec<u8>>,
75    /// Key size override (if algorithm supports multiple sizes)
76    pub key_size: Option<usize>,
77}
78
79/// Trait for AEAD (Authenticated Encryption with Associated Data)
80pub trait AEAD {
81    /// Nonce size in bytes
82    const NONCE_SIZE: usize;
83    /// Tag size in bytes
84    const TAG_SIZE: usize;
85
86    /// Encrypt plaintext with associated data
87    fn encrypt(
88        &self,
89        key: &SecretKey,
90        nonce: &[u8],
91        plaintext: &[u8],
92        associated_data: &[u8],
93    ) -> Result<Vec<u8>>;
94
95    /// Decrypt ciphertext with associated data
96    fn decrypt(
97        &self,
98        key: &SecretKey,
99        nonce: &[u8],
100        ciphertext: &[u8],
101        associated_data: &[u8],
102    ) -> Result<Vec<u8>>;
103}
104
105/// Runtime AEAD adapter supporting ChaCha20-Poly1305 and AES-GCM
106pub struct RuntimeAead;
107
108impl RuntimeAead {
109    fn check_key_len(key: &SecretKey, expected: usize) -> Result<()> {
110        if key.expose_secret().len() != expected {
111            return Err(Error::crypto(
112                "key_validation",
113                &format!(
114                    "invalid key size: expected {}, got {}",
115                    expected,
116                    key.expose_secret().len()
117                ),
118            ));
119        }
120        Ok(())
121    }
122}
123
124impl crate::crypto::AEAD for RuntimeAead {
125    // Maximum nonce size (XChaCha20Poly1305 uses 24 bytes, others use 12)
126    const NONCE_SIZE: usize = 24;
127    const TAG_SIZE: usize = 16;
128
129    fn encrypt(
130        &self,
131        key: &SecretKey,
132        nonce: &[u8],
133        plaintext: &[u8],
134        associated_data: &[u8],
135    ) -> Result<Vec<u8>> {
136        // Please note: Each algorithm validates its own nonce size
137        match key.algorithm() {
138            Algorithm::ChaCha20Poly1305 => {
139                Self::check_key_len(key, 32)?;
140                if nonce.len() != 12 {
141                    return Err(Error::crypto(
142                        "nonce_validation",
143                        &format!(
144                            "ChaCha20Poly1305 requires 12-byte nonce, got {}",
145                            nonce.len()
146                        ),
147                    ));
148                }
149                let cipher = ChaCha20Poly1305::new(ChaChaKey::from_slice(key.expose_secret()));
150                // annotate nonce type to help type inference
151                let n: &ChaChaNonce = ChaChaNonce::from_slice(nonce);
152                cipher
153                    .encrypt(
154                        n,
155                        Payload {
156                            msg: plaintext,
157                            aad: associated_data,
158                        },
159                    )
160                    .map_err(|e| {
161                        Error::crypto(
162                            "encrypt",
163                            &format!("ChaCha20-Poly1305 encryption failed: {}", e),
164                        )
165                    })
166            }
167            Algorithm::XChaCha20Poly1305 => {
168                Self::check_key_len(key, 32)?;
169                if nonce.len() != 24 {
170                    return Err(Error::crypto(
171                        "nonce_validation",
172                        &format!(
173                            "XChaCha20Poly1305 requires 24-byte nonce, got {}",
174                            nonce.len()
175                        ),
176                    ));
177                }
178                let cipher = XChaCha20Poly1305::new(ChaChaKey::from_slice(key.expose_secret()));
179                let n: &XNonce = XNonce::from_slice(nonce);
180                cipher
181                    .encrypt(
182                        n,
183                        Payload {
184                            msg: plaintext,
185                            aad: associated_data,
186                        },
187                    )
188                    .map_err(|e| {
189                        Error::crypto(
190                            "encrypt",
191                            &format!("XChaCha20-Poly1305 encryption failed: {}", e),
192                        )
193                    })
194            }
195            Algorithm::Aes256Gcm => {
196                Self::check_key_len(key, 32)?;
197                if nonce.len() != 12 {
198                    return Err(Error::crypto(
199                        "nonce_validation",
200                        &format!("AES-256-GCM requires 12-byte nonce, got {}", nonce.len()),
201                    ));
202                }
203                let cipher = Aes256Gcm::new(AesKey::from_slice(key.expose_secret()));
204                let n: &AesNonce = AesNonce::from_slice(nonce);
205                cipher
206                    .encrypt(
207                        n,
208                        Payload {
209                            msg: plaintext,
210                            aad: associated_data,
211                        },
212                    )
213                    .map_err(|e| {
214                        Error::crypto("encrypt", &format!("AES-256-GCM encryption failed: {}", e))
215                    })
216            }
217            alg => Err(Error::crypto(
218                "encrypt",
219                &format!("algorithm {alg:?} not supported for AEAD"),
220            )),
221        }
222    }
223
224    fn decrypt(
225        &self,
226        key: &SecretKey,
227        nonce: &[u8],
228        ciphertext: &[u8],
229        associated_data: &[u8],
230    ) -> Result<Vec<u8>> {
231        // Note: Each algorithm validates its own nonce size
232        match key.algorithm() {
233            Algorithm::ChaCha20Poly1305 => {
234                Self::check_key_len(key, 32)?;
235                if nonce.len() != 12 {
236                    return Err(Error::crypto(
237                        "nonce_validation",
238                        &format!(
239                            "ChaCha20Poly1305 requires 12-byte nonce, got {}",
240                            nonce.len()
241                        ),
242                    ));
243                }
244                let cipher = ChaCha20Poly1305::new(ChaChaKey::from_slice(key.expose_secret()));
245                let n: &ChaChaNonce = ChaChaNonce::from_slice(nonce);
246                cipher
247                    .decrypt(
248                        n,
249                        Payload {
250                            msg: ciphertext,
251                            aad: associated_data,
252                        },
253                    )
254                    .map_err(|e| {
255                        Error::crypto(
256                            "decrypt",
257                            &format!("ChaCha20-Poly1305 decryption failed: {}", e),
258                        )
259                    })
260            }
261            Algorithm::XChaCha20Poly1305 => {
262                Self::check_key_len(key, 32)?;
263                if nonce.len() != 24 {
264                    return Err(Error::crypto(
265                        "nonce_validation",
266                        &format!(
267                            "XChaCha20Poly1305 requires 24-byte nonce, got {}",
268                            nonce.len()
269                        ),
270                    ));
271                }
272                let cipher = XChaCha20Poly1305::new(ChaChaKey::from_slice(key.expose_secret()));
273                let n: &XNonce = XNonce::from_slice(nonce);
274                cipher
275                    .decrypt(
276                        n,
277                        Payload {
278                            msg: ciphertext,
279                            aad: associated_data,
280                        },
281                    )
282                    .map_err(|e| {
283                        Error::crypto(
284                            "decrypt",
285                            &format!("XChaCha20-Poly1305 decryption failed: {}", e),
286                        )
287                    })
288            }
289            Algorithm::Aes256Gcm => {
290                Self::check_key_len(key, 32)?;
291                if nonce.len() != 12 {
292                    return Err(Error::crypto(
293                        "nonce_validation",
294                        &format!("AES-256-GCM requires 12-byte nonce, got {}", nonce.len()),
295                    ));
296                }
297                let cipher = Aes256Gcm::new(AesKey::from_slice(key.expose_secret()));
298                let n: &AesNonce = AesNonce::from_slice(nonce);
299                cipher
300                    .decrypt(
301                        n,
302                        Payload {
303                            msg: ciphertext,
304                            aad: associated_data,
305                        },
306                    )
307                    .map_err(|e| {
308                        Error::crypto("decrypt", &format!("AES-256-GCM decryption failed: {}", e))
309                    })
310            }
311            alg => Err(Error::crypto(
312                "decrypt",
313                &format!("algorithm {alg:?} not supported for AEAD"),
314            )),
315        }
316    }
317}
318
319/// Nonce generation strategies
320#[derive(Debug, Clone, Copy)]
321pub enum NonceStrategy {
322    /// Random nonces (requires large nonce space)
323    Random,
324    /// Counter-based (requires persistent state)
325    Counter,
326    /// Derived from message (requires unique messages)
327    Synthetic,
328}
329
330/// Trait for nonce generation
331pub trait NonceGenerator {
332    /// Generate a nonce for encryption
333    ///
334    /// CRITICAL: Nonce must never be reused with the same key to avoid breaking security
335    fn generate_nonce(&mut self, message_id: &[u8]) -> Result<Vec<u8>>;
336
337    /// Get the strategy this generator uses
338    fn strategy(&self) -> NonceStrategy;
339}
340
341/// Nonce generator using a cryptographically secure RNG
342pub struct RandomNonceGenerator<R: SecureRandom> {
343    rng: R,
344    nonce_size: usize,
345}
346
347/// Random nonce generator implementation
348impl<R: SecureRandom> RandomNonceGenerator<R> {
349    /// Create a new random nonce generator
350    pub fn new(rng: R, nonce_size: usize) -> Self {
351        Self { rng, nonce_size }
352    }
353}
354
355impl<R: SecureRandom> NonceGenerator for RandomNonceGenerator<R> {
356    fn generate_nonce(&mut self, _message_id: &[u8]) -> Result<Vec<u8>> {
357        let mut nonce = vec![0u8; self.nonce_size];
358        self.rng.fill_secure_bytes(&mut nonce)?;
359        Ok(nonce)
360    }
361
362    fn strategy(&self) -> NonceStrategy {
363        NonceStrategy::Random
364    }
365}
366
367/// Blanket implementation for any crypto RNG
368impl<T> SecureRandom for T where T: RngCore + CryptoRng {}
369
370/// Trait for constant-time operations
371pub trait ConstantTime {
372    /// Compare two byte slices in constant time
373    fn ct_eq(&self, other: &[u8]) -> bool;
374
375    /// Select between two values in constant time
376    fn ct_select(condition: bool, a: Self, b: Self) -> Self
377    where
378        Self: Sized;
379}
380
381#[cfg(test)]
382mod tests {
383    use rand_chacha::ChaCha12Rng;
384    use rand_core::SeedableRng;
385
386    use super::*;
387
388    #[test]
389    fn test_keygen_params() {
390        let params = KeyGenParams {
391            algorithm: Algorithm::Aes256Gcm,
392            seed: None,
393            key_size: Some(32),
394        };
395        assert_eq!(params.algorithm, Algorithm::Aes256Gcm);
396        assert_eq!(params.key_size, Some(32));
397        assert_eq!(params.seed, None);
398    }
399
400    #[test]
401    fn test_end_to_end_aead() {
402        // Create deterministic RNG for testing and generate a key
403        let mut rng = ChaCha12Rng::seed_from_u64(42);
404        let generator = SimpleSymmetricKeyGenerator;
405        let key = generator.generate(&mut rng).unwrap();
406
407        // Create AEAD and nonce generator
408        let aead = RuntimeAead;
409        // ChaCha20Poly1305 needs 12-byte nonces
410        let mut nonce_gen = RandomNonceGenerator::new(
411            ChaCha12Rng::seed_from_u64(123),
412            12, // ChaCha20Poly1305 nonce size
413        );
414
415        // Let's test the data
416        let plaintext = b"Secret message for testing";
417        let associated_data = b"public metadata";
418
419        // Encrypt
420        let nonce = nonce_gen.generate_nonce(b"msg_001").unwrap();
421        let ciphertext = aead
422            .encrypt(&key, &nonce, plaintext, associated_data)
423            .unwrap();
424
425        // Verify that the ciphertext is different from the plaintext
426        assert_ne!(ciphertext.as_slice(), plaintext);
427
428        // Decrypt and finally verify
429        let decrypted = aead
430            .decrypt(&key, &nonce, &ciphertext, associated_data)
431            .unwrap();
432        assert_eq!(decrypted.as_slice(), plaintext);
433    }
434}