1use crate::chacha20::ChaCha20;
46use crate::classic::crypto_aead_chacha20poly1305_impl::impl_chacha20poly1305_aead;
47use crate::constants::{
48 CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES,
49 CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
50 CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES,
51};
52use crate::types::*;
53
54pub type Mac = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
56pub type Nonce = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES];
58pub type Key = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES];
60
61impl_chacha20poly1305_aead! {
62 abytes: CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES,
63 messagebytes_max: CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
66 stream: |nonce: &Nonce, key: &Key| ChaCha20::ietf(key, nonce, 0),
67 key: Key,
68 nonce: Nonce,
69 mac: Mac,
70
71 keygen_inplace: crypto_aead_chacha20poly1305_ietf_keygen_inplace,
73
74 keygen: crypto_aead_chacha20poly1305_ietf_keygen,
76
77 encrypt_detached: crypto_aead_chacha20poly1305_ietf_encrypt_detached,
87
88 encrypt_detached_inplace: crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace,
95
96 decrypt_detached: crypto_aead_chacha20poly1305_ietf_decrypt_detached,
108
109 decrypt_detached_inplace: crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace,
119
120 encrypt: crypto_aead_chacha20poly1305_ietf_encrypt,
129
130 decrypt: crypto_aead_chacha20poly1305_ietf_decrypt,
141
142 encrypt_inplace: crypto_aead_chacha20poly1305_ietf_encrypt_inplace,
152
153 decrypt_inplace: crypto_aead_chacha20poly1305_ietf_decrypt_inplace,
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 #[cfg(dryoc_native_tests)]
172 use crate::classic::crypto_aead_chacha20poly1305_impl::test_util::check_matches_libsodium;
173 use crate::classic::crypto_aead_chacha20poly1305_impl::test_util::{
174 Aead, check_failures_leave_outputs_untouched,
175 };
176 use crate::error::{Error, LengthConstraint};
177 use crate::test_prelude::*;
178
179 #[test]
180 fn test_message_len_bound_is_ietf_max() {
181 const MAX: usize = CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX;
182 const ABYTES: usize = CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES;
183
184 assert!(matches!(
185 message_len_from_combined_len(MAX + ABYTES, crate::ErrorContext::Ciphertext),
186 Ok(len) if len == MAX
187 ));
188 if let Some(combined_len) = MAX.checked_add(ABYTES + 1) {
189 assert!(matches!(
190 message_len_from_combined_len(combined_len, crate::ErrorContext::Ciphertext),
191 Err(Error::InvalidLength {
192 context: crate::ErrorContext::Message,
193 actual,
194 constraint: LengthConstraint::AtMost(max),
195 }) if actual == MAX + 1 && max == MAX
196 ));
197 }
198 }
199
200 const MESSAGE: &[u8] =
201 b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
202 const AD: &[u8] = &[
203 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
204 ];
205 const KEY: Key = [
206 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
207 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
208 0x9e, 0x9f,
209 ];
210 const NONCE: Nonce = [
211 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
212 ];
213
214 fn expected() -> Vec<u8> {
215 hex::decode(concat!(
216 "d31a8d34648e60db7b86afbc53ef7ec2",
217 "a4aded51296e08fea9e2b5a736ee62d6",
218 "3dbea45e8ca9671282fafb69da92728b",
219 "1a71de0a9e060b2905d6a5b67ecd3b36",
220 "92ddbd7f2d778b8c9803aee328091b58",
221 "fab324e4fad675945585808b4831d7bc",
222 "3ff4def08e4b7a9de576d26586cec64b",
223 "61161ae10b594f09e26a7e902ecbd0600691"
224 ))
225 .expect("valid test vector")
226 }
227
228 #[test]
229 fn test_rfc_8439_known_answer() {
230 let mut ciphertext = vec![0u8; MESSAGE.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
231 crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, MESSAGE, Some(AD), &NONCE, &KEY)
232 .expect("encrypt");
233 assert_eq!(ciphertext, expected());
234
235 let mut decrypted = vec![0u8; MESSAGE.len()];
236 crypto_aead_chacha20poly1305_ietf_decrypt(
237 &mut decrypted,
238 &ciphertext,
239 Some(AD),
240 &NONCE,
241 &KEY,
242 )
243 .expect("decrypt");
244 assert_eq!(decrypted, MESSAGE);
245 }
246
247 #[test]
248 fn test_detached_and_inplace_match_combined() {
249 let expected = expected();
250 let mut detached = MESSAGE.to_vec();
251 let mut mac = Mac::default();
252 crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
253 &mut detached,
254 &mut mac,
255 Some(AD),
256 &NONCE,
257 &KEY,
258 )
259 .expect("detached encrypt");
260 assert_eq!(detached, expected[..MESSAGE.len()]);
261 assert_eq!(mac, expected[MESSAGE.len()..]);
262
263 crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
264 &mut detached,
265 &mac,
266 Some(AD),
267 &NONCE,
268 &KEY,
269 )
270 .expect("detached decrypt");
271 assert_eq!(detached, MESSAGE);
272
273 let mut combined = MESSAGE.to_vec();
274 combined.resize(MESSAGE.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, 0);
275 crypto_aead_chacha20poly1305_ietf_encrypt_inplace(&mut combined, Some(AD), &NONCE, &KEY)
276 .expect("in-place encrypt");
277 assert_eq!(combined, expected);
278 }
279
280 #[test]
281 fn test_empty_message_and_length_errors() {
282 let mut ciphertext = [0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
283 crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, &[], None, &NONCE, &KEY)
284 .expect("empty encrypt");
285 crypto_aead_chacha20poly1305_ietf_decrypt(&mut [], &ciphertext, None, &NONCE, &KEY)
286 .expect("empty decrypt");
287
288 assert!(
289 crypto_aead_chacha20poly1305_ietf_decrypt(
290 &mut [],
291 &[0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES - 1],
292 None,
293 &NONCE,
294 &KEY,
295 )
296 .is_err()
297 );
298 assert!(
299 crypto_aead_chacha20poly1305_ietf_encrypt(&mut [0u8; 1], &[], None, &NONCE, &KEY,)
300 .is_err()
301 );
302 }
303
304 fn rfc8439_state(key: &Key, nonce: &Nonce) -> [u32; 16] {
309 let mut state = [0u32; 16];
310 state[..4].copy_from_slice(&crate::utils::SIGMA);
311 for (word, bytes) in state[4..12].iter_mut().zip(key.as_chunks::<4>().0) {
312 *word = u32::from_le_bytes(*bytes);
313 }
314 for (word, bytes) in state[13..].iter_mut().zip(nonce.as_chunks::<4>().0) {
315 *word = u32::from_le_bytes(*bytes);
316 }
317 state
318 }
319
320 #[test]
325 fn test_stream_crosses_ietf_counter_boundary() {
326 for nonce in [NONCE, [0xffu8; 12]] {
327 let state = rfc8439_state(&KEY, &nonce);
328 let nonce_word = u32::from_le_bytes(nonce[..4].try_into().unwrap());
329 let start = u64::from(u32::MAX) | (u64::from(nonce_word) << 32);
330 let next = u64::from(nonce_word.wrapping_add(1)) << 32;
331 assert_eq!(next, start.wrapping_add(1));
332
333 let mut expected = [0u8; 128];
334 let (first, second) = expected.split_at_mut(64);
335 crate::chacha20::scalar_block(&state, start, first.try_into().unwrap());
336 crate::chacha20::scalar_block(&state, next, second.try_into().unwrap());
337 assert_ne!(first, second);
338
339 let mut stream = [0u8; 128];
340 ChaCha20::ietf(&KEY, &nonce, u32::MAX).apply_keystream(&mut stream);
341 assert_eq!(stream, expected, "nonce {nonce:02x?}");
342 }
343 }
344
345 fn aead() -> Aead<Nonce> {
346 Aead {
347 encrypt_detached: crypto_aead_chacha20poly1305_ietf_encrypt_detached,
348 encrypt_detached_inplace: crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace,
349 decrypt_detached: crypto_aead_chacha20poly1305_ietf_decrypt_detached,
350 decrypt_detached_inplace: crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace,
351 encrypt: crypto_aead_chacha20poly1305_ietf_encrypt,
352 decrypt: crypto_aead_chacha20poly1305_ietf_decrypt,
353 encrypt_inplace: crypto_aead_chacha20poly1305_ietf_encrypt_inplace,
354 decrypt_inplace: crypto_aead_chacha20poly1305_ietf_decrypt_inplace,
355 }
356 }
357
358 #[test]
359 fn test_failures_leave_outputs_untouched() {
360 check_failures_leave_outputs_untouched(&aead(), &KEY, &NONCE);
361 }
362
363 #[cfg(dryoc_native_tests)]
368 #[test]
369 fn test_final_counter_blocks_match_libsodium() {
370 use libc::c_ulonglong;
371 use libsodium_sys::{crypto_stream_chacha20_ietf_xor_ic, crypto_stream_chacha20_xor_ic};
372
373 crate::native_test_util::init();
374
375 for nonce in [NONCE, [0xffu8; 12]] {
376 let mut stream = [0u8; 128];
377 ChaCha20::ietf(&KEY, &nonce, u32::MAX).apply_keystream(&mut stream);
378
379 let mut expected = [0u8; 128];
380 let start = u64::from(u32::MAX)
381 | (u64::from(u32::from_le_bytes(nonce[..4].try_into().unwrap())) << 32);
382 let rc = unsafe {
385 crypto_stream_chacha20_xor_ic(
386 expected.as_mut_ptr(),
387 expected.as_ptr(),
388 expected.len() as c_ulonglong,
389 nonce[4..].as_ptr(),
390 start,
391 KEY.as_ptr(),
392 )
393 };
394 assert_eq!(rc, 0);
395 assert_eq!(stream, expected, "nonce {nonce:02x?}");
396
397 let mut final_block = [0u8; 64];
398 let rc = unsafe {
400 crypto_stream_chacha20_ietf_xor_ic(
401 final_block.as_mut_ptr(),
402 final_block.as_ptr(),
403 final_block.len() as c_ulonglong,
404 nonce.as_ptr(),
405 u32::MAX,
406 KEY.as_ptr(),
407 )
408 };
409 assert_eq!(rc, 0);
410 assert_eq!(
411 stream[..64],
412 final_block,
413 "nonce {nonce:02x?}, ietf final block"
414 );
415 }
416 }
417
418 #[cfg(dryoc_native_tests)]
419 #[test]
420 fn test_matches_libsodium_detached_and_combined() {
421 crate::native_test_util::init();
422 check_matches_libsodium(
423 &aead(),
424 libsodium_sys::crypto_aead_chacha20poly1305_ietf_encrypt_detached,
425 &KEY,
426 &NONCE,
427 );
428 }
429
430 #[cfg(dryoc_native_tests)]
431 #[test]
432 fn test_libsodium_constants() {
433 use libsodium_sys::{
434 crypto_aead_chacha20poly1305_ietf_abytes, crypto_aead_chacha20poly1305_ietf_keybytes,
435 crypto_aead_chacha20poly1305_ietf_messagebytes_max,
436 crypto_aead_chacha20poly1305_ietf_npubbytes,
437 crypto_aead_chacha20poly1305_ietf_nsecbytes,
438 };
439
440 use crate::constants::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES;
441
442 crate::native_test_util::init();
443
444 unsafe {
447 assert_eq!(
448 crypto_aead_chacha20poly1305_ietf_keybytes(),
449 CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES
450 );
451 assert_eq!(
452 crypto_aead_chacha20poly1305_ietf_nsecbytes(),
453 CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES
454 );
455 assert_eq!(
456 crypto_aead_chacha20poly1305_ietf_npubbytes(),
457 CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES
458 );
459 assert_eq!(
460 crypto_aead_chacha20poly1305_ietf_abytes(),
461 CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES
462 );
463 assert_eq!(
464 crypto_aead_chacha20poly1305_ietf_messagebytes_max(),
465 CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
466 );
467 }
468 }
469
470 #[cfg(dryoc_native_tests)]
471 #[test]
472 fn test_libsodium_interop() {
473 use crate::native_test_util::{
474 crypto_aead_chacha20poly1305_ietf_decrypt as open,
475 crypto_aead_chacha20poly1305_ietf_encrypt as seal,
476 };
477
478 let ciphertext = expected();
479 assert_eq!(
480 open(&ciphertext, Some(AD), &NONCE, &KEY).expect("libsodium open"),
481 MESSAGE
482 );
483
484 let sodium_ciphertext = seal(MESSAGE, Some(AD), &NONCE, &KEY);
485 let mut plaintext = vec![0u8; MESSAGE.len()];
486 crypto_aead_chacha20poly1305_ietf_decrypt(
487 &mut plaintext,
488 &sodium_ciphertext,
489 Some(AD),
490 &NONCE,
491 &KEY,
492 )
493 .expect("dryoc decrypt");
494 assert_eq!(plaintext, MESSAGE);
495 }
496}