1use zeroize::Zeroize;
55
56use crate::chacha20::ChaCha20;
57use crate::classic::crypto_aead_chacha20poly1305_impl::impl_chacha20poly1305_aead;
58use crate::classic::crypto_core::{HChaCha20Key, crypto_core_hchacha20};
59use crate::constants::{
60 CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES, CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES,
61 CRYPTO_AEAD_XCHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
62 CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES, CRYPTO_CORE_HCHACHA20_INPUTBYTES,
63};
64use crate::types::*;
65
66pub type Mac = [u8; CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES];
68pub type Nonce = [u8; CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES];
70pub type Key = [u8; CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES];
72
73fn xchacha20_stream(nonce: &Nonce, key: &Key, counter: u64) -> ChaCha20 {
76 let mut subkey = HChaCha20Key::default();
77 crypto_core_hchacha20(
78 &mut subkey,
79 nonce
80 .first_chunk::<CRYPTO_CORE_HCHACHA20_INPUTBYTES>()
81 .expect("XChaCha20 nonce holds the HChaCha20 input"),
82 key,
83 None,
84 );
85
86 let nonce_tail = nonce
91 .last_chunk()
92 .expect("XChaCha20 nonce ends with the ChaCha20 nonce");
93 let cipher = ChaCha20::legacy(&subkey, nonce_tail, counter);
94 subkey.zeroize();
95 cipher
96}
97
98impl_chacha20poly1305_aead! {
99 abytes: CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES,
100 messagebytes_max: CRYPTO_AEAD_XCHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
103 stream: |nonce: &Nonce, key: &Key| xchacha20_stream(nonce, key, 0),
104 key: Key,
105 nonce: Nonce,
106 mac: Mac,
107
108 keygen_inplace: crypto_aead_xchacha20poly1305_ietf_keygen_inplace,
110
111 keygen: crypto_aead_xchacha20poly1305_ietf_keygen,
113
114 encrypt_detached: crypto_aead_xchacha20poly1305_ietf_encrypt_detached,
124
125 encrypt_detached_inplace: crypto_aead_xchacha20poly1305_ietf_encrypt_detached_inplace,
132
133 decrypt_detached: crypto_aead_xchacha20poly1305_ietf_decrypt_detached,
145
146 decrypt_detached_inplace: crypto_aead_xchacha20poly1305_ietf_decrypt_detached_inplace,
156
157 encrypt: crypto_aead_xchacha20poly1305_ietf_encrypt,
166
167 decrypt: crypto_aead_xchacha20poly1305_ietf_decrypt,
178
179 encrypt_inplace: crypto_aead_xchacha20poly1305_ietf_encrypt_inplace,
189
190 decrypt_inplace: crypto_aead_xchacha20poly1305_ietf_decrypt_inplace,
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 #[cfg(dryoc_native_tests)]
209 use crate::classic::crypto_aead_chacha20poly1305_impl::test_util::check_matches_libsodium;
210 use crate::classic::crypto_aead_chacha20poly1305_impl::test_util::{
211 Aead, check_failures_leave_outputs_untouched,
212 };
213 use crate::error::{Error, LengthConstraint};
214
215 #[test]
216 fn test_message_len_bound_is_xchacha_max() {
217 const MAX: usize = CRYPTO_AEAD_XCHACHA20POLY1305_IETF_MESSAGEBYTES_MAX;
218 const ABYTES: usize = CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES;
219
220 assert!(matches!(
225 message_len_from_combined_len(MAX + ABYTES, crate::ErrorContext::Ciphertext),
226 Ok(len) if len == MAX
227 ));
228 assert!(matches!(
229 message_len_from_combined_len(ABYTES - 1, crate::ErrorContext::Ciphertext),
230 Err(Error::InvalidLength {
231 context: crate::ErrorContext::Ciphertext,
232 constraint: LengthConstraint::AtLeast(ABYTES),
233 ..
234 })
235 ));
236 }
237
238 const MESSAGE: &[u8] =
239 b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
240 const AD: &[u8] = &[
241 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
242 ];
243 const KEY: Key = [
244 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
245 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
246 0x9e, 0x9f,
247 ];
248 const NONCE: Nonce = [
249 0xf2, 0x8a, 0x50, 0xa7, 0x8a, 0x7e, 0x23, 0xc9, 0xcb, 0xa6, 0x78, 0x34, 0x66, 0xf8, 0x03,
250 0x59, 0x0f, 0x04, 0xe9, 0x22, 0x31, 0xa3, 0x2d, 0x5d,
251 ];
252 const EXPECTED: &[u8] = &[
253 0x20, 0xf1, 0xae, 0x75, 0xe1, 0xe5, 0xe0, 0x00, 0x40, 0x29, 0x4f, 0x0f, 0xb1, 0x0e, 0xbb,
254 0x08, 0x10, 0xc5, 0x93, 0xc7, 0xdb, 0xa4, 0xec, 0x10, 0x4c, 0x1e, 0x5e, 0xf9, 0x50, 0x7f,
255 0xae, 0xef, 0x58, 0xfc, 0x28, 0x98, 0xbb, 0xd0, 0xe4, 0x7b, 0x2f, 0x53, 0x31, 0xfb, 0xc3,
256 0x67, 0xd3, 0xc2, 0x78, 0x4e, 0x36, 0x48, 0xce, 0x1e, 0xaa, 0x77, 0x87, 0xad, 0x18, 0x6d,
257 0xb2, 0x68, 0x5e, 0xe8, 0x9a, 0xe4, 0xd3, 0x44, 0x1f, 0x6e, 0xa0, 0xb2, 0x22, 0x4c, 0xd5,
258 0xa1, 0x34, 0x16, 0x1b, 0x55, 0x4d, 0x8b, 0x48, 0x35, 0x0b, 0x4a, 0xd4, 0x01, 0x15, 0xdb,
259 0x81, 0xea, 0x82, 0x09, 0x68, 0xe9, 0x43, 0x89, 0x2f, 0x2b, 0x80, 0x51, 0xcb, 0x5f, 0x7a,
260 0x86, 0x66, 0xe7, 0xe7, 0xef, 0x7f, 0x84, 0xc0, 0xa2, 0xf8, 0x0a, 0x12, 0xd0, 0x66, 0x80,
261 0xc8, 0xee, 0xbb, 0xd9, 0x30, 0x04, 0x10, 0x9d, 0xe8, 0x42,
262 ];
263
264 #[test]
265 fn test_known_answer() {
266 let mut ciphertext = vec![0u8; MESSAGE.len() + CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES];
267 crypto_aead_xchacha20poly1305_ietf_encrypt(
268 &mut ciphertext,
269 MESSAGE,
270 Some(AD),
271 &NONCE,
272 &KEY,
273 )
274 .expect("encrypt");
275 assert_eq!(ciphertext, EXPECTED);
276
277 let mut decrypted = vec![0u8; MESSAGE.len()];
278 crypto_aead_xchacha20poly1305_ietf_decrypt(
279 &mut decrypted,
280 &ciphertext,
281 Some(AD),
282 &NONCE,
283 &KEY,
284 )
285 .expect("decrypt");
286 assert_eq!(decrypted, MESSAGE);
287 }
288
289 #[test]
290 fn test_detached_matches_combined() {
291 let mut combined = vec![0u8; MESSAGE.len() + CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES];
292 crypto_aead_xchacha20poly1305_ietf_encrypt(&mut combined, MESSAGE, Some(AD), &NONCE, &KEY)
293 .expect("encrypt");
294
295 let mut detached = vec![0u8; MESSAGE.len()];
296 let mut mac = Mac::default();
297 crypto_aead_xchacha20poly1305_ietf_encrypt_detached(
298 &mut detached,
299 &mut mac,
300 MESSAGE,
301 Some(AD),
302 &NONCE,
303 &KEY,
304 )
305 .expect("detached encrypt");
306
307 assert_eq!(detached, combined[..MESSAGE.len()]);
308 assert_eq!(mac.as_slice(), &combined[MESSAGE.len()..]);
309 }
310
311 #[test]
312 fn test_empty_message_and_no_aad() {
313 let mut ciphertext = vec![0u8; CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES];
314 crypto_aead_xchacha20poly1305_ietf_encrypt(&mut ciphertext, &[], None, &NONCE, &KEY)
315 .expect("encrypt");
316
317 let mut decrypted = vec![];
318 crypto_aead_xchacha20poly1305_ietf_decrypt(&mut decrypted, &ciphertext, None, &NONCE, &KEY)
319 .expect("decrypt");
320 assert!(decrypted.is_empty());
321 }
322
323 #[test]
324 fn test_inplace_roundtrip() {
325 let mut data = MESSAGE.to_vec();
326 data.resize(MESSAGE.len() + CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES, 0);
327 crypto_aead_xchacha20poly1305_ietf_encrypt_inplace(&mut data, Some(AD), &NONCE, &KEY)
328 .expect("inplace encrypt");
329 assert_eq!(data, EXPECTED);
330
331 crypto_aead_xchacha20poly1305_ietf_decrypt_inplace(&mut data, Some(AD), &NONCE, &KEY)
332 .expect("inplace decrypt");
333 assert_eq!(&data[..MESSAGE.len()], MESSAGE);
334 }
335
336 fn xchacha20_state(nonce: &Nonce, key: &Key) -> [u32; 16] {
341 let mut subkey = HChaCha20Key::default();
342 crypto_core_hchacha20(
343 &mut subkey,
344 nonce.first_chunk::<16>().expect("16-byte prefix"),
345 key,
346 None,
347 );
348 let mut state = [0u32; 16];
349 state[..4].copy_from_slice(&crate::utils::SIGMA);
350 for (word, bytes) in state[4..12].iter_mut().zip(subkey.as_chunks::<4>().0) {
351 *word = u32::from_le_bytes(*bytes);
352 }
353 for (word, bytes) in state[14..].iter_mut().zip(nonce[16..].as_chunks::<4>().0) {
354 *word = u32::from_le_bytes(*bytes);
355 }
356 state
357 }
358
359 #[test]
364 fn test_xietf_ext_stream_crosses_counter_boundaries() {
365 let state = xchacha20_state(&NONCE, &KEY);
366 for start in [u64::from(u32::MAX), u64::MAX] {
367 let mut expected = [0u8; 128];
368 let (first, second) = expected.split_at_mut(64);
369 crate::chacha20::scalar_block(&state, start, first.try_into().unwrap());
370 crate::chacha20::scalar_block(
371 &state,
372 start.wrapping_add(1),
373 second.try_into().unwrap(),
374 );
375 assert_ne!(first, second);
376
377 let mut stream = [0u8; 128];
378 xchacha20_stream(&NONCE, &KEY, start).apply_keystream(&mut stream);
379 assert_eq!(stream, expected, "from {start:#x}");
380 }
381 }
382
383 fn aead() -> Aead<Nonce> {
384 Aead {
385 encrypt_detached: crypto_aead_xchacha20poly1305_ietf_encrypt_detached,
386 encrypt_detached_inplace: crypto_aead_xchacha20poly1305_ietf_encrypt_detached_inplace,
387 decrypt_detached: crypto_aead_xchacha20poly1305_ietf_decrypt_detached,
388 decrypt_detached_inplace: crypto_aead_xchacha20poly1305_ietf_decrypt_detached_inplace,
389 encrypt: crypto_aead_xchacha20poly1305_ietf_encrypt,
390 decrypt: crypto_aead_xchacha20poly1305_ietf_decrypt,
391 encrypt_inplace: crypto_aead_xchacha20poly1305_ietf_encrypt_inplace,
392 decrypt_inplace: crypto_aead_xchacha20poly1305_ietf_decrypt_inplace,
393 }
394 }
395
396 #[test]
397 fn test_failures_leave_outputs_untouched() {
398 check_failures_leave_outputs_untouched(&aead(), &KEY, &NONCE);
399 }
400
401 #[cfg(dryoc_native_tests)]
402 mod native_tests {
403 use super::*;
404
405 #[test]
406 fn test_libsodium_interop() {
407 use crate::native_test_util::{
408 crypto_aead_xchacha20poly1305_ietf_decrypt as open,
409 crypto_aead_xchacha20poly1305_ietf_encrypt as seal,
410 };
411
412 let mut ciphertext =
413 vec![0u8; MESSAGE.len() + CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES];
414 crypto_aead_xchacha20poly1305_ietf_encrypt(
415 &mut ciphertext,
416 MESSAGE,
417 Some(AD),
418 &NONCE,
419 &KEY,
420 )
421 .expect("encrypt");
422 let so_plaintext = open(&ciphertext, Some(AD), &NONCE, &KEY).expect("libsodium open");
423 assert_eq!(so_plaintext, MESSAGE);
424
425 let so_ciphertext = seal(MESSAGE, Some(AD), &NONCE, &KEY);
426 let mut plaintext = vec![0u8; MESSAGE.len()];
427 crypto_aead_xchacha20poly1305_ietf_decrypt(
428 &mut plaintext,
429 &so_ciphertext,
430 Some(AD),
431 &NONCE,
432 &KEY,
433 )
434 .expect("decrypt");
435 assert_eq!(plaintext, MESSAGE);
436 }
437
438 #[test]
442 fn test_counter_boundaries_match_libsodium_xchacha_stream() {
443 use libsodium_sys::crypto_stream_xchacha20_xor_ic;
444
445 crate::native_test_util::init();
446
447 for start in [u64::from(u32::MAX), u64::MAX] {
448 let input = [0u8; 128];
449 let mut expected = [0u8; 128];
450 unsafe {
454 assert_eq!(
455 crypto_stream_xchacha20_xor_ic(
456 expected.as_mut_ptr(),
457 input.as_ptr(),
458 input.len() as u64,
459 NONCE.as_ptr(),
460 start,
461 KEY.as_ptr(),
462 ),
463 0
464 );
465 }
466
467 let mut actual = [0u8; 128];
468 xchacha20_stream(&NONCE, &KEY, start).apply_keystream(&mut actual);
469 assert_eq!(actual, expected, "from {start:#x}");
470 }
471 }
472
473 #[test]
474 fn test_matches_libsodium_detached_and_combined() {
475 crate::native_test_util::init();
476 check_matches_libsodium(
477 &aead(),
478 libsodium_sys::crypto_aead_xchacha20poly1305_ietf_encrypt_detached,
479 &KEY,
480 &NONCE,
481 );
482 }
483 }
484}