1use 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
11type AesKey = aes_gcm::Key<Aes256Gcm>;
13type AesNonce = aes_gcm::Nonce<U12>;
14
15pub trait SecureRandom: RngCore + CryptoRng {
17 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
26pub trait KeyGenerator {
28 type Key;
30
31 fn generate<R: SecureRandom>(&self, rng: &mut R) -> Result<Self::Key>;
33
34 fn generate_with_params<R: SecureRandom>(
36 &self,
37 rng: &mut R,
38 params: KeyGenParams,
39 ) -> Result<Self::Key>;
40}
41pub 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; 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#[derive(Debug, Clone)]
70pub struct KeyGenParams {
71 pub algorithm: Algorithm,
73 pub seed: Option<Vec<u8>>,
75 pub key_size: Option<usize>,
77}
78
79pub trait AEAD {
81 const NONCE_SIZE: usize;
83 const TAG_SIZE: usize;
85
86 fn encrypt(
88 &self,
89 key: &SecretKey,
90 nonce: &[u8],
91 plaintext: &[u8],
92 associated_data: &[u8],
93 ) -> Result<Vec<u8>>;
94
95 fn decrypt(
97 &self,
98 key: &SecretKey,
99 nonce: &[u8],
100 ciphertext: &[u8],
101 associated_data: &[u8],
102 ) -> Result<Vec<u8>>;
103}
104
105pub 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 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 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 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 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#[derive(Debug, Clone, Copy)]
321pub enum NonceStrategy {
322 Random,
324 Counter,
326 Synthetic,
328}
329
330pub trait NonceGenerator {
332 fn generate_nonce(&mut self, message_id: &[u8]) -> Result<Vec<u8>>;
336
337 fn strategy(&self) -> NonceStrategy;
339}
340
341pub struct RandomNonceGenerator<R: SecureRandom> {
343 rng: R,
344 nonce_size: usize,
345}
346
347impl<R: SecureRandom> RandomNonceGenerator<R> {
349 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
367impl<T> SecureRandom for T where T: RngCore + CryptoRng {}
369
370pub trait ConstantTime {
372 fn ct_eq(&self, other: &[u8]) -> bool;
374
375 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 let mut rng = ChaCha12Rng::seed_from_u64(42);
404 let generator = SimpleSymmetricKeyGenerator;
405 let key = generator.generate(&mut rng).unwrap();
406
407 let aead = RuntimeAead;
409 let mut nonce_gen = RandomNonceGenerator::new(
411 ChaCha12Rng::seed_from_u64(123),
412 12, );
414
415 let plaintext = b"Secret message for testing";
417 let associated_data = b"public metadata";
418
419 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 assert_ne!(ciphertext.as_slice(), plaintext);
427
428 let decrypted = aead
430 .decrypt(&key, &nonce, &ciphertext, associated_data)
431 .unwrap();
432 assert_eq!(decrypted.as_slice(), plaintext);
433 }
434}