1#![forbid(unsafe_code)]
2
3extern crate alloc;
46
47pub mod aes_gcm_siv;
48pub mod ccm;
49pub mod deoxys;
50pub(crate) mod deoxys_bc;
51pub mod keywrap;
52pub mod nonce_seq;
53pub mod ocb3_impl;
54pub mod sealed_box;
55pub mod stream;
56pub mod xchacha20;
57
58pub use aes_gcm_siv::{AesGcmSiv128, AesGcmSiv256};
59pub use ccm::{Aes128Ccm, Aes256Ccm};
60pub use deoxys::Deoxys2_128;
61pub use keywrap::{aes128_key_unwrap, aes128_key_wrap, aes256_key_unwrap, aes256_key_wrap};
62pub use nonce_seq::{Nonce12, Nonce24, NonceSequence};
63pub use ocb3_impl::{Aes128Ocb3, Aes256Ocb3};
64pub use sealed_box::{open_box, seal_box};
65pub use stream::{Aes256GcmStream, ChaCha20Poly1305Stream};
66pub use xchacha20::XChaCha20Poly1305;
67
68pub fn seal_with_random_nonce(
88 aead: &dyn oxicrypto_core::Aead,
89 key: &[u8],
90 aad: &[u8],
91 plaintext: &[u8],
92 rng: &mut dyn oxicrypto_core::Rng,
93) -> Result<(alloc::vec::Vec<u8>, alloc::vec::Vec<u8>), oxicrypto_core::CryptoError> {
94 let nonce_len = aead.nonce_len();
95 let mut nonce = alloc::vec![0u8; nonce_len];
96 rng.fill(&mut nonce)?;
97 let ct = aead.seal_to_vec(key, &nonce, aad, plaintext)?;
98 Ok((nonce, ct))
99}
100
101use aead::{AeadInPlace, KeyInit, KeySizeUser};
102use oxicrypto_core::{Aead, CryptoError};
103
104struct AeadParams {
108 key_len: usize,
109 nonce_len: usize,
110 tag_len: usize,
111}
112
113fn seal_in_place<C: AeadInPlace + KeyInit>(
118 key: &[u8],
119 nonce: &[u8],
120 aad: &[u8],
121 pt: &[u8],
122 ct_out: &mut [u8],
123 params: AeadParams,
124) -> Result<usize, CryptoError> {
125 if key.len() != params.key_len {
126 return Err(CryptoError::InvalidKey);
127 }
128 if nonce.len() != params.nonce_len {
129 return Err(CryptoError::InvalidNonce);
130 }
131 let required = pt
132 .len()
133 .checked_add(params.tag_len)
134 .ok_or(CryptoError::BadInput)?;
135 if ct_out.len() < required {
136 return Err(CryptoError::BufferTooSmall);
137 }
138
139 ct_out[..pt.len()].copy_from_slice(pt);
141
142 let cipher = C::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
143 let nonce_arr = aead::generic_array::GenericArray::from_slice(nonce);
144 let tag = cipher
145 .encrypt_in_place_detached(nonce_arr, aad, &mut ct_out[..pt.len()])
146 .map_err(|_| CryptoError::Internal("AEAD encrypt failed"))?;
147 ct_out[pt.len()..required].copy_from_slice(&tag);
148 Ok(required)
149}
150
151fn open_in_place<C: AeadInPlace + KeyInit>(
157 key: &[u8],
158 nonce: &[u8],
159 aad: &[u8],
160 ct: &[u8],
161 pt_out: &mut [u8],
162 params: AeadParams,
163) -> Result<usize, CryptoError> {
164 if key.len() != params.key_len {
165 return Err(CryptoError::InvalidKey);
166 }
167 if nonce.len() != params.nonce_len {
168 return Err(CryptoError::InvalidNonce);
169 }
170 if ct.len() < params.tag_len {
171 return Err(CryptoError::BadInput);
172 }
173 let pt_len = ct.len() - params.tag_len;
174 if pt_out.len() < pt_len {
175 return Err(CryptoError::BufferTooSmall);
176 }
177
178 pt_out[..pt_len].copy_from_slice(&ct[..pt_len]);
179
180 let cipher = C::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
181 let nonce_arr = aead::generic_array::GenericArray::from_slice(nonce);
182 let tag_bytes = &ct[pt_len..];
183 if tag_bytes.len() != params.tag_len {
184 return Err(CryptoError::BadInput);
185 }
186 let tag = aead::Tag::<C>::clone_from_slice(tag_bytes);
187
188 cipher
189 .decrypt_in_place_detached(nonce_arr, aad, &mut pt_out[..pt_len], &tag)
190 .map_err(|_| CryptoError::InvalidTag)?;
191
192 Ok(pt_len)
193}
194
195#[derive(Debug, Default, Clone, Copy)]
201pub struct Aes128Gcm;
202
203impl Aead for Aes128Gcm {
204 fn name(&self) -> &'static str {
205 "AES-128-GCM"
206 }
207 fn key_len(&self) -> usize {
208 aes_gcm::Aes128Gcm::key_size()
209 }
210 fn nonce_len(&self) -> usize {
211 12
212 }
213 fn tag_len(&self) -> usize {
214 16
215 }
216 fn seal(
217 &self,
218 key: &[u8],
219 nonce: &[u8],
220 aad: &[u8],
221 pt: &[u8],
222 ct_out: &mut [u8],
223 ) -> Result<usize, CryptoError> {
224 seal_in_place::<aes_gcm::Aes128Gcm>(
225 key,
226 nonce,
227 aad,
228 pt,
229 ct_out,
230 AeadParams {
231 key_len: 16,
232 nonce_len: 12,
233 tag_len: 16,
234 },
235 )
236 }
237 fn open(
238 &self,
239 key: &[u8],
240 nonce: &[u8],
241 aad: &[u8],
242 ct: &[u8],
243 pt_out: &mut [u8],
244 ) -> Result<usize, CryptoError> {
245 open_in_place::<aes_gcm::Aes128Gcm>(
246 key,
247 nonce,
248 aad,
249 ct,
250 pt_out,
251 AeadParams {
252 key_len: 16,
253 nonce_len: 12,
254 tag_len: 16,
255 },
256 )
257 }
258}
259
260#[derive(Debug, Default, Clone, Copy)]
266pub struct Aes256Gcm;
267
268impl Aead for Aes256Gcm {
269 fn name(&self) -> &'static str {
270 "AES-256-GCM"
271 }
272 fn key_len(&self) -> usize {
273 aes_gcm::Aes256Gcm::key_size()
274 }
275 fn nonce_len(&self) -> usize {
276 12
277 }
278 fn tag_len(&self) -> usize {
279 16
280 }
281 fn seal(
282 &self,
283 key: &[u8],
284 nonce: &[u8],
285 aad: &[u8],
286 pt: &[u8],
287 ct_out: &mut [u8],
288 ) -> Result<usize, CryptoError> {
289 seal_in_place::<aes_gcm::Aes256Gcm>(
290 key,
291 nonce,
292 aad,
293 pt,
294 ct_out,
295 AeadParams {
296 key_len: 32,
297 nonce_len: 12,
298 tag_len: 16,
299 },
300 )
301 }
302 fn open(
303 &self,
304 key: &[u8],
305 nonce: &[u8],
306 aad: &[u8],
307 ct: &[u8],
308 pt_out: &mut [u8],
309 ) -> Result<usize, CryptoError> {
310 open_in_place::<aes_gcm::Aes256Gcm>(
311 key,
312 nonce,
313 aad,
314 ct,
315 pt_out,
316 AeadParams {
317 key_len: 32,
318 nonce_len: 12,
319 tag_len: 16,
320 },
321 )
322 }
323}
324
325#[derive(Debug, Default, Clone, Copy)]
331pub struct ChaCha20Poly1305;
332
333impl Aead for ChaCha20Poly1305 {
334 fn name(&self) -> &'static str {
335 "ChaCha20-Poly1305"
336 }
337 fn key_len(&self) -> usize {
338 chacha20poly1305::ChaCha20Poly1305::key_size()
339 }
340 fn nonce_len(&self) -> usize {
341 12
342 }
343 fn tag_len(&self) -> usize {
344 16
345 }
346 fn seal(
347 &self,
348 key: &[u8],
349 nonce: &[u8],
350 aad: &[u8],
351 pt: &[u8],
352 ct_out: &mut [u8],
353 ) -> Result<usize, CryptoError> {
354 seal_in_place::<chacha20poly1305::ChaCha20Poly1305>(
355 key,
356 nonce,
357 aad,
358 pt,
359 ct_out,
360 AeadParams {
361 key_len: 32,
362 nonce_len: 12,
363 tag_len: 16,
364 },
365 )
366 }
367 fn open(
368 &self,
369 key: &[u8],
370 nonce: &[u8],
371 aad: &[u8],
372 ct: &[u8],
373 pt_out: &mut [u8],
374 ) -> Result<usize, CryptoError> {
375 open_in_place::<chacha20poly1305::ChaCha20Poly1305>(
376 key,
377 nonce,
378 aad,
379 ct,
380 pt_out,
381 AeadParams {
382 key_len: 32,
383 nonce_len: 12,
384 tag_len: 16,
385 },
386 )
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 const KEY_128: [u8; 16] = [0x42u8; 16];
395 const KEY_256: [u8; 32] = [0x42u8; 32];
396 const NONCE_12: [u8; 12] = [0x24u8; 12];
397 const AAD: &[u8] = b"additional authenticated data";
398 const PLAINTEXT: &[u8] = b"hello, oxicrypto!";
399
400 fn round_trip<A: Aead>(aead: &A, key: &[u8]) {
401 let mut ct = vec![0u8; PLAINTEXT.len() + aead.tag_len()];
402 let written = aead
403 .seal(key, &NONCE_12, AAD, PLAINTEXT, &mut ct)
404 .expect("seal failed");
405 assert_eq!(written, PLAINTEXT.len() + aead.tag_len());
406
407 let mut pt = vec![0u8; PLAINTEXT.len()];
408 let recovered = aead
409 .open(key, &NONCE_12, AAD, &ct[..written], &mut pt)
410 .expect("open failed");
411 assert_eq!(recovered, PLAINTEXT.len());
412 assert_eq!(&pt[..recovered], PLAINTEXT);
413 }
414
415 fn wrong_key_fails<A: Aead>(aead: &A, good_key: &[u8], wrong_key: &[u8]) {
416 let mut ct = vec![0u8; PLAINTEXT.len() + aead.tag_len()];
417 let written = aead
418 .seal(good_key, &NONCE_12, AAD, PLAINTEXT, &mut ct)
419 .unwrap();
420
421 let mut pt = vec![0u8; PLAINTEXT.len()];
422 let result = aead.open(wrong_key, &NONCE_12, AAD, &ct[..written], &mut pt);
423 assert_eq!(result, Err(CryptoError::InvalidTag));
424 }
425
426 #[test]
427 fn aes128gcm_round_trip() {
428 round_trip(&Aes128Gcm, &KEY_128);
429 }
430
431 #[test]
432 fn aes256gcm_round_trip() {
433 round_trip(&Aes256Gcm, &KEY_256);
434 }
435
436 #[test]
437 fn chacha20poly1305_round_trip() {
438 round_trip(&ChaCha20Poly1305, &KEY_256);
439 }
440
441 #[test]
442 fn aes128gcm_wrong_key_fails() {
443 wrong_key_fails(&Aes128Gcm, &KEY_128, &[0x00u8; 16]);
444 }
445
446 #[test]
447 fn aes256gcm_wrong_key_fails() {
448 wrong_key_fails(&Aes256Gcm, &KEY_256, &[0x00u8; 32]);
449 }
450
451 #[test]
452 fn chacha20poly1305_wrong_key_fails() {
453 wrong_key_fails(&ChaCha20Poly1305, &KEY_256, &[0x00u8; 32]);
454 }
455
456 #[test]
457 fn invalid_key_length() {
458 let aead = Aes256Gcm;
459 let mut ct = vec![0u8; PLAINTEXT.len() + 16];
460 let result = aead.seal(&[0u8; 16], &NONCE_12, AAD, PLAINTEXT, &mut ct);
461 assert_eq!(result, Err(CryptoError::InvalidKey));
462 }
463
464 struct CounterRng {
468 counter: u8,
469 }
470
471 impl CounterRng {
472 fn new() -> Self {
473 Self { counter: 0x11 }
474 }
475 }
476
477 impl oxicrypto_core::Rng for CounterRng {
478 fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
479 for b in dst.iter_mut() {
480 *b = self.counter;
481 self.counter = self.counter.wrapping_add(1);
482 }
483 Ok(())
484 }
485 }
486
487 #[test]
488 fn seal_with_random_nonce_aes128gcm_round_trip() {
489 let aead = Aes128Gcm;
490 let mut rng = CounterRng::new();
491
492 let (nonce, ct) = seal_with_random_nonce(&aead, &KEY_128, AAD, PLAINTEXT, &mut rng)
493 .expect("seal_with_random_nonce failed");
494
495 assert_eq!(
496 nonce.len(),
497 aead.nonce_len(),
498 "nonce length must match aead.nonce_len()"
499 );
500 assert_eq!(
501 ct.len(),
502 PLAINTEXT.len() + aead.tag_len(),
503 "ct length must be pt+tag"
504 );
505
506 let expected_nonce: alloc::vec::Vec<u8> =
508 (0u8..12).map(|i| 0x11_u8.wrapping_add(i)).collect();
509 assert_eq!(nonce, expected_nonce, "nonce must match RNG output");
510
511 let recovered = aead
513 .open_to_vec(&KEY_128, &nonce, AAD, &ct)
514 .expect("open_to_vec after seal_with_random_nonce failed");
515 assert_eq!(
516 recovered.as_slice(),
517 PLAINTEXT,
518 "round-trip must recover plaintext"
519 );
520 }
521
522 #[test]
523 fn seal_with_random_nonce_returns_separate_nonce_and_ct() {
524 let aead = Aes256Gcm;
526 let mut rng = CounterRng::new();
527
528 let (nonce, ct) = seal_with_random_nonce(&aead, &KEY_256, AAD, PLAINTEXT, &mut rng)
529 .expect("seal_with_random_nonce failed");
530
531 assert_eq!(ct.len(), PLAINTEXT.len() + aead.tag_len());
533 assert_eq!(nonce.len(), aead.nonce_len());
535 assert_ne!(
536 nonce.as_slice(),
537 &ct[..nonce.len()],
538 "nonce must not be embedded in ct"
539 );
540 }
541
542 #[test]
543 fn seal_with_random_nonce_rng_failure_propagates() {
544 struct AlwaysFailRng;
545 impl oxicrypto_core::Rng for AlwaysFailRng {
546 fn fill(&mut self, _dst: &mut [u8]) -> Result<(), CryptoError> {
547 Err(CryptoError::Rng)
548 }
549 }
550 let aead = Aes128Gcm;
551 let result = seal_with_random_nonce(&aead, &KEY_128, AAD, PLAINTEXT, &mut AlwaysFailRng);
552 assert_eq!(result, Err(CryptoError::Rng), "RNG failure must propagate");
553 }
554}