1#[cfg(feature = "zeroize")]
2use zeroize::Zeroize;
3
4use super::{CHACHA20_IETF_MAX_LEN, ChaCha, hchacha20};
5use crate::{Aead, AeadError, Hash, StreamCipher, bytes::Bytes, poly1305::Poly1305};
6
7#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
22pub struct ChaCha20Poly1305 {
23 key: [u8; 32],
24}
25
26impl ChaCha20Poly1305 {
27 pub fn new(key: &[u8; 32]) -> ChaCha20Poly1305 {
29 return ChaCha20Poly1305 {
30 key: *key,
31 };
32 }
33
34 #[inline]
36 fn poly1305_key_gen(&self, nonce: &[u8; 12]) -> ([u8; 32], ChaCha<20, true>) {
37 let mut cipher = ChaCha::<20, true>::new(&self.key, nonce);
38 cipher.set_counter(0);
39 let mut block = [0u8; 64];
40 cipher.xor_keystream(&mut block);
41 let mut key = [0u8; 32];
42 key.copy_from_slice(&block[..32]);
43 #[cfg(feature = "zeroize")]
44 block.zeroize();
45 return (key, cipher);
46 }
47}
48
49impl Aead for ChaCha20Poly1305 {
50 const KEY_SIZE: usize = 32;
51 const TAG_SIZE: usize = 16;
52 const NONCE_SIZE: usize = 12;
53
54 fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
55 assert!(
56 in_out.len() as u64 <= CHACHA20_IETF_MAX_LEN,
57 "ChaCha20-Poly1305 plaintext exceeds maximum allowed length (RFC 8439 limit)"
58 );
59
60 let nonce: &[u8; 12] = nonce.try_into().expect("nonce must be 12 bytes");
61 #[cfg_attr(not(feature = "zeroize"), allow(unused_mut))]
62 let (mut poly1305_key, mut cipher) = self.poly1305_key_gen(nonce);
63
64 cipher.set_counter(1);
65 cipher.xor_keystream(in_out);
66
67 let mut mac = Poly1305::new(&poly1305_key);
68 #[cfg(feature = "zeroize")]
69 poly1305_key.zeroize();
70 update_poly1305_padded(&mut mac, aad);
71 update_poly1305_padded(&mut mac, in_out);
72 mac.update(&(aad.len() as u64).to_le_bytes());
73 mac.update(&(in_out.len() as u64).to_le_bytes());
74 let tag_bytes = mac.finalize();
75
76 let mut tag = Hash(Bytes::<64>::with_length(16));
77 tag.as_mut().copy_from_slice(&tag_bytes);
78 return tag;
79 }
80
81 fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
82 if in_out.len() as u64 > CHACHA20_IETF_MAX_LEN {
83 return Err(AeadError::InvalidCiphertext);
84 }
85 if tag.len() != Self::TAG_SIZE {
86 return Err(AeadError::InvalidCiphertext);
87 }
88 let nonce: &[u8; 12] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
89 #[cfg_attr(not(feature = "zeroize"), allow(unused_mut))]
90 let (mut poly1305_key, mut cipher) = self.poly1305_key_gen(nonce);
91
92 let mut mac = Poly1305::new(&poly1305_key);
93 #[cfg(feature = "zeroize")]
94 poly1305_key.zeroize();
95 update_poly1305_padded(&mut mac, aad);
96 update_poly1305_padded(&mut mac, in_out);
97 mac.update(&(aad.len() as u64).to_le_bytes());
98 mac.update(&(in_out.len() as u64).to_le_bytes());
99 let computed = mac.finalize();
100
101 if !constant_time_eq::constant_time_eq(&computed, tag) {
102 return Err(AeadError::InvalidCiphertext);
103 }
104
105 cipher.set_counter(1);
106 cipher.xor_keystream(in_out);
107
108 return Ok(());
109 }
110}
111
112#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
130pub struct XChaCha20Poly1305 {
131 key: [u8; 32],
132}
133
134impl XChaCha20Poly1305 {
135 pub fn new(key: &[u8; 32]) -> XChaCha20Poly1305 {
136 return XChaCha20Poly1305 {
137 key: *key,
138 };
139 }
140
141 #[inline]
142 fn derive_subkey(&self, nonce: &[u8; 24]) -> ([u8; 32], [u8; 12]) {
143 let subkey = hchacha20(&self.key, nonce[..16].try_into().unwrap());
144 let mut ietf_nonce = [0u8; 12];
145 ietf_nonce[4..12].copy_from_slice(&nonce[16..24]);
146 return (subkey, ietf_nonce);
147 }
148}
149
150impl Aead for XChaCha20Poly1305 {
151 const KEY_SIZE: usize = 32;
152 const TAG_SIZE: usize = 16;
153 const NONCE_SIZE: usize = 24;
154
155 fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
156 assert!(
157 in_out.len() as u64 <= CHACHA20_IETF_MAX_LEN,
158 "XChaCha20-Poly1305 plaintext exceeds maximum allowed length (RFC 8439 limit)"
159 );
160
161 let nonce: &[u8; 24] = nonce.try_into().expect("nonce must be 24 bytes");
162 #[cfg_attr(not(feature = "zeroize"), allow(unused_mut))]
163 let (mut subkey, ietf_nonce) = self.derive_subkey(nonce);
164
165 let mut keygen = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
166 keygen.set_counter(0);
167 let mut block = [0u8; 64];
168 keygen.xor_keystream(&mut block);
169 let mut otk = [0u8; 32];
170 otk.copy_from_slice(&block[..32]);
171 #[cfg(feature = "zeroize")]
172 block.zeroize();
173
174 let mut cipher = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
175 cipher.set_counter(1);
176 cipher.xor_keystream(in_out);
177 #[cfg(feature = "zeroize")]
178 subkey.zeroize();
179
180 let mut mac = Poly1305::new(&otk);
181 #[cfg(feature = "zeroize")]
182 otk.zeroize();
183 update_poly1305_padded(&mut mac, aad);
184 update_poly1305_padded(&mut mac, in_out);
185 mac.update(&(aad.len() as u64).to_le_bytes());
186 mac.update(&(in_out.len() as u64).to_le_bytes());
187 let tag_bytes = mac.finalize();
188
189 let mut tag = Hash(Bytes::<64>::with_length(16));
190 tag.as_mut().copy_from_slice(&tag_bytes);
191 return tag;
192 }
193
194 fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
195 if in_out.len() as u64 > CHACHA20_IETF_MAX_LEN {
196 return Err(AeadError::InvalidCiphertext);
197 }
198 if tag.len() != Self::TAG_SIZE {
199 return Err(AeadError::InvalidCiphertext);
200 }
201 let nonce: &[u8; 24] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
202 #[cfg_attr(not(feature = "zeroize"), allow(unused_mut))]
203 let (mut subkey, ietf_nonce) = self.derive_subkey(nonce);
204
205 let mut keygen = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
206 keygen.set_counter(0);
207 let mut block = [0u8; 64];
208 keygen.xor_keystream(&mut block);
209 let mut otk = [0u8; 32];
210 otk.copy_from_slice(&block[..32]);
211 #[cfg(feature = "zeroize")]
212 block.zeroize();
213
214 let mut cipher = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
215 cipher.set_counter(1);
216 #[cfg(feature = "zeroize")]
217 subkey.zeroize();
218
219 let mut mac = Poly1305::new(&otk);
220 update_poly1305_padded(&mut mac, aad);
221 update_poly1305_padded(&mut mac, in_out);
222 mac.update(&(aad.len() as u64).to_le_bytes());
223 mac.update(&(in_out.len() as u64).to_le_bytes());
224 let computed = mac.finalize();
225 #[cfg(feature = "zeroize")]
226 otk.zeroize();
227
228 if !constant_time_eq::constant_time_eq(&computed, tag) {
229 in_out.fill(0);
230 return Err(AeadError::InvalidCiphertext);
231 }
232
233 cipher.xor_keystream(in_out);
234
235 return Ok(());
236 }
237}
238
239#[inline]
240pub(crate) fn update_poly1305_padded(mac: &mut Poly1305, data: &[u8]) {
241 mac.update(data);
242 let rem = data.len() % 16;
243 if rem != 0 {
244 let pad = [0u8; 15];
245 mac.update(&pad[..16 - rem]);
246 }
247}
248
249#[cfg(test)]
250mod test {
251 use super::ChaCha20Poly1305;
252 use crate::Aead;
253
254 #[test]
256 fn poly1305_key_gen_vectors() {
257 struct KeyGenTest {
258 key_hex: &'static str,
259 nonce_hex: &'static str,
260 expected_otk_hex: &'static str,
261 }
262
263 let tests = [
264 KeyGenTest {
265 key_hex: "0000000000000000000000000000000000000000000000000000000000000000",
266 nonce_hex: "000000000000000000000000",
267 expected_otk_hex: "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7",
268 },
269 KeyGenTest {
270 key_hex: "0000000000000000000000000000000000000000000000000000000000000001",
271 nonce_hex: "000000000000000000000002",
272 expected_otk_hex: "ecfa254f845f647473d3cb140da9e87606cb33066c447b87bc2666dde3fbb739",
273 },
274 KeyGenTest {
275 key_hex: "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
276 nonce_hex: "000000000000000000000002",
277 expected_otk_hex: "965e3bc6f9ec7ed9560808f4d229f94b137ff275ca9b3fcbdd59deaad23310ae",
278 },
279 ];
280
281 for (i, test) in tests.iter().enumerate() {
282 let key: [u8; 32] = hex::decode(test.key_hex).unwrap().try_into().unwrap();
283 let nonce: [u8; 12] = hex::decode(test.nonce_hex).unwrap().try_into().unwrap();
284
285 let ae = ChaCha20Poly1305::new(&key);
286 let (otk, _) = ae.poly1305_key_gen(&nonce);
287 let expected_otk = hex::decode(test.expected_otk_hex).unwrap();
288
289 assert_eq!(otk.as_slice(), expected_otk.as_slice(), "key gen test [{i}] failed");
290 }
291 }
292
293 #[test]
295 fn aead_decrypt_test() {
296 let key: [u8; 32] = hex::decode("1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0")
297 .unwrap()
298 .try_into()
299 .unwrap();
300 let nonce: [u8; 12] = hex::decode("000000000102030405060708").unwrap().try_into().unwrap();
301 let aad = hex::decode("f33388860000000000004e91").unwrap();
302
303 let ciphertext = hex::decode(concat!(
304 "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2",
305 "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf",
306 "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855",
307 "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4",
308 "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e",
309 "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a",
310 "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10",
311 "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29",
312 "a6ad5cb4022b02709b",
313 ))
314 .unwrap();
315
316 let tag = hex::decode("eead9d67890cbb22392336fea1851f38").unwrap();
317
318 let ae = ChaCha20Poly1305::new(&key);
319
320 let mut decrypted = ciphertext.clone();
321 ae.decrypt_in_place(&mut decrypted, &nonce, &aad, &tag).unwrap();
322
323 let expected_plaintext = hex::decode(concat!(
324 "496e7465726e65742d4472616674732061726520647261667420646f63756d65",
325 "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d",
326 "6f6e74687320616e64206d617920626520757064617465642c207265706c6163",
327 "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65",
328 "6e747320617420616e792074696d652e20497420697320696e617070726f7072",
329 "6961746520746f2075736520496e7465726e65742d4472616674732061732072",
330 "65666572656e6365206d6174657269616c206f7220746f206369746520746865",
331 "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67",
332 "726573732e2fe2809d",
333 ))
334 .unwrap();
335
336 assert_eq!(decrypted, expected_plaintext);
337 }
338
339 #[test]
341 fn aead_roundtrip() {
342 let key: [u8; 32] = [0x55; 32];
343 let nonce: [u8; 12] = [0xaa; 12];
344 let aad = b"associated data";
345 let plaintext = b"hello, world!";
346
347 let ae = ChaCha20Poly1305::new(&key);
348
349 let mut ciphertext = plaintext.to_vec();
350 let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, aad);
351
352 let mut decrypted = ciphertext.clone();
353 ae.decrypt_in_place(&mut decrypted, &nonce, aad, tag.as_ref()).unwrap();
354 assert_eq!(decrypted, plaintext);
355 }
356
357 #[test]
359 fn aead_tampered_tag_fails() {
360 let key: [u8; 32] = [0x55; 32];
361 let nonce: [u8; 12] = [0xaa; 12];
362
363 let ae = ChaCha20Poly1305::new(&key);
364
365 let mut ciphertext = b"secret".to_vec();
366 let mut tag = ae.encrypt_in_place(&mut ciphertext, &nonce, b"");
367 tag.as_mut()[0] ^= 1;
368
369 let mut decrypted = ciphertext.clone();
370 let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
371 assert!(result.is_err());
372 }
373
374 #[test]
376 fn aead_tampered_ciphertext_fails() {
377 let key: [u8; 32] = [0x55; 32];
378 let nonce: [u8; 12] = [0xaa; 12];
379
380 let ae = ChaCha20Poly1305::new(&key);
381
382 let mut ciphertext = b"secret".to_vec();
383 let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, b"");
384
385 ciphertext[3] ^= 1;
386
387 let mut decrypted = ciphertext.clone();
388 let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
389 assert!(result.is_err());
390 }
391
392 #[test]
394 fn aead_wrong_nonce_fails() {
395 let key: [u8; 32] = [0x55; 32];
396
397 let ae = ChaCha20Poly1305::new(&key);
398
399 let mut ciphertext = b"secret".to_vec();
400 let tag = ae.encrypt_in_place(&mut ciphertext, &[0u8; 12], b"");
401
402 let mut decrypted = ciphertext.clone();
403 let result = ae.decrypt_in_place(&mut decrypted, &[1u8; 12], b"", tag.as_ref());
404 assert!(result.is_err());
405 }
406
407 #[test]
410 fn wycheproof_chacha20_poly1305_vectors() {
411 let data: serde_json::Value = serde_json::from_str(include_str!(
412 "../../testdata/wycheproof/testvectors_v1/chacha20_poly1305_test.json"
413 ))
414 .unwrap();
415 let mut valid_tested = 0u64;
416 let mut invalid_tested = 0u64;
417 for group in data["testGroups"].as_array().unwrap() {
418 if group["keySize"].as_u64() != Some(256) {
419 continue;
420 }
421 if group["ivSize"].as_u64() != Some(96) {
422 continue;
423 }
424 if group["tagSize"].as_u64() != Some(128) {
425 continue;
426 }
427 for test in group["tests"].as_array().unwrap() {
428 let key_hex = test["key"].as_str().unwrap();
429 let iv_hex = test["iv"].as_str().unwrap();
430 let msg_hex = test["msg"].as_str().unwrap();
431 let aad_hex = test["aad"].as_str().unwrap();
432 let ct_hex = test["ct"].as_str().unwrap();
433 let tag_hex = test["tag"].as_str().unwrap();
434 let result = test["result"].as_str().unwrap();
435
436 let key: [u8; 32] = hex::decode(key_hex).unwrap().try_into().unwrap();
437 let nonce: [u8; 12] = hex::decode(iv_hex).unwrap().try_into().unwrap();
438 let expected_ct = hex::decode(ct_hex).unwrap();
439 let expected_tag: [u8; 16] = hex::decode(tag_hex).unwrap().try_into().unwrap();
440 let pt = hex::decode(msg_hex).unwrap();
441 let aad = hex::decode(aad_hex).unwrap();
442
443 let cipher = ChaCha20Poly1305::new(&key);
444
445 if result == "valid" {
446 let mut buf = pt.clone();
447 let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
448 assert_eq!(
449 buf, expected_ct,
450 "wycheproof ChaCha20-Poly1305 tcId={} ct mismatch",
451 test["tcId"]
452 );
453 assert_eq!(
454 tag.as_ref(),
455 &expected_tag[..],
456 "wycheproof ChaCha20-Poly1305 tcId={} tag mismatch",
457 test["tcId"]
458 );
459
460 let mut buf2 = expected_ct.clone();
461 cipher
462 .decrypt_in_place(&mut buf2, &nonce, &aad, &expected_tag)
463 .expect("wycheproof ChaCha20-Poly1305 decrypt failed");
464 assert_eq!(buf2, pt, "wycheproof ChaCha20-Poly1305 tcId={} pt mismatch", test["tcId"]);
465 valid_tested += 1;
466 } else {
467 let mut buf = expected_ct.clone();
468 let res = cipher.decrypt_in_place(&mut buf, &nonce, &aad, &expected_tag);
469 assert!(
470 res.is_err(),
471 "wycheproof ChaCha20-Poly1305 tcId={} expected invalid but passed",
472 test["tcId"]
473 );
474 invalid_tested += 1;
475 }
476 }
477 }
478 assert!(valid_tested > 0, "no valid ChaCha20-Poly1305 wycheproof tests were run");
479 assert!(invalid_tested > 0, "no invalid ChaCha20-Poly1305 wycheproof tests were run");
480 }
481}
482
483#[cfg(test)]
484mod xchacha20poly1305_tests {
485 use super::XChaCha20Poly1305;
486 use crate::{Aead, StreamCipher};
487
488 #[test]
490 fn aead_xchacha20poly1305_test_vector() {
491 let key: [u8; 32] = hex::decode("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
492 .unwrap()
493 .try_into()
494 .unwrap();
495 let nonce: [u8; 24] = hex::decode("404142434445464748494a4b4c4d4e4f5051525354555657")
496 .unwrap()
497 .try_into()
498 .unwrap();
499 let aad = hex::decode("50515253c0c1c2c3c4c5c6c7").unwrap();
500
501 let plaintext = hex::decode(concat!(
502 "4c616469657320616e642047656e746c",
503 "656d656e206f662074686520636c6173",
504 "73206f66202739393a20496620492063",
505 "6f756c64206f6666657220796f75206f",
506 "6e6c79206f6e652074697020666f7220",
507 "746865206675747572652c2073756e73",
508 "637265656e20776f756c642062652069",
509 "742e",
510 ))
511 .unwrap();
512
513 let ae = XChaCha20Poly1305::new(&key);
515 let (subkey, _ietf_nonce) = ae.derive_subkey(&nonce);
516 let mut keygen = crate::chacha::ChaCha::<20, true>::new(&subkey, &_ietf_nonce);
518 keygen.set_counter(0);
519 let mut block = [0u8; 64];
520 keygen.xor_keystream(&mut block);
521 let expected_otk = hex::decode("7b191f80f361f099094f6f4b8fb97df847cc6873a8f2b190dd73807183f907d5").unwrap();
522 assert_eq!(&block[..32], expected_otk.as_slice(), "Poly1305 key derivation failed");
523
524 let mut ciphertext = plaintext.clone();
526 let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, &aad);
527
528 let expected_ciphertext = hex::decode(concat!(
529 "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb",
530 "731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452",
531 "2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9",
532 "21f9664c97637da9768812f615c68b13b52e",
533 ))
534 .unwrap();
535 assert_eq!(ciphertext, expected_ciphertext, "ciphertext mismatch");
536
537 let expected_tag = hex::decode("c0875924c1c7987947deafd8780acf49").unwrap();
538 assert_eq!(tag.as_ref(), expected_tag.as_slice(), "tag mismatch");
539
540 let mut decrypted = ciphertext.clone();
542 ae.decrypt_in_place(&mut decrypted, &nonce, &aad, tag.as_ref()).unwrap();
543 assert_eq!(decrypted, plaintext);
544 }
545
546 #[test]
547 fn aead_xchacha20poly1305_roundtrip() {
548 let key: [u8; 32] = [0x55; 32];
549 let nonce: [u8; 24] = [0xaa; 24];
550 let aad = b"associated data";
551 let plaintext = b"hello, world!";
552
553 let ae = XChaCha20Poly1305::new(&key);
554
555 let mut ciphertext = plaintext.to_vec();
556 let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, aad);
557
558 let mut decrypted = ciphertext.clone();
559 ae.decrypt_in_place(&mut decrypted, &nonce, aad, tag.as_ref()).unwrap();
560 assert_eq!(decrypted, plaintext);
561 }
562
563 #[test]
564 fn aead_xchacha20poly1305_tampered_tag_fails() {
565 let key: [u8; 32] = [0x55; 32];
566 let nonce: [u8; 24] = [0xaa; 24];
567
568 let ae = XChaCha20Poly1305::new(&key);
569
570 let mut ciphertext = b"secret".to_vec();
571 let mut tag = ae.encrypt_in_place(&mut ciphertext, &nonce, b"");
572 tag.as_mut()[0] ^= 1;
573
574 let mut decrypted = ciphertext.clone();
575 let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
576 assert!(result.is_err());
577 assert!(decrypted.iter().all(|b| *b == 0));
578 }
579
580 #[test]
581 fn aead_xchacha20poly1305_wrong_nonce_fails() {
582 let key: [u8; 32] = [0x55; 32];
583
584 let ae = XChaCha20Poly1305::new(&key);
585
586 let mut ciphertext = b"secret".to_vec();
587 let tag = ae.encrypt_in_place(&mut ciphertext, &[0u8; 24], b"");
588
589 let mut decrypted = ciphertext.clone();
590 let result = ae.decrypt_in_place(&mut decrypted, &[1u8; 24], b"", tag.as_ref());
591 assert!(result.is_err());
592 }
593
594 #[test]
597 fn wycheproof_xchacha20_poly1305_vectors() {
598 let data: serde_json::Value = serde_json::from_str(include_str!(
599 "../../testdata/wycheproof/testvectors_v1/xchacha20_poly1305_test.json"
600 ))
601 .unwrap();
602 let mut valid_tested = 0u64;
603 let mut invalid_tested = 0u64;
604 for group in data["testGroups"].as_array().unwrap() {
605 if group["keySize"].as_u64() != Some(256) {
606 continue;
607 }
608 if group["ivSize"].as_u64() != Some(192) {
609 continue;
610 }
611 if group["tagSize"].as_u64() != Some(128) {
612 continue;
613 }
614 for test in group["tests"].as_array().unwrap() {
615 let key_hex = test["key"].as_str().unwrap();
616 let iv_hex = test["iv"].as_str().unwrap();
617 let msg_hex = test["msg"].as_str().unwrap();
618 let aad_hex = test["aad"].as_str().unwrap();
619 let ct_hex = test["ct"].as_str().unwrap();
620 let tag_hex = test["tag"].as_str().unwrap();
621 let result = test["result"].as_str().unwrap();
622
623 let key: [u8; 32] = hex::decode(key_hex).unwrap().try_into().unwrap();
624 let nonce: [u8; 24] = hex::decode(iv_hex).unwrap().try_into().unwrap();
625 let expected_ct = hex::decode(ct_hex).unwrap();
626 let expected_tag: [u8; 16] = hex::decode(tag_hex).unwrap().try_into().unwrap();
627 let pt = hex::decode(msg_hex).unwrap();
628 let aad = hex::decode(aad_hex).unwrap();
629
630 let cipher = XChaCha20Poly1305::new(&key);
631
632 if result == "valid" {
633 let mut buf = pt.clone();
634 let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
635 assert_eq!(
636 buf, expected_ct,
637 "wycheproof XChaCha20-Poly1305 tcId={} ct mismatch",
638 test["tcId"]
639 );
640 assert_eq!(
641 tag.as_ref(),
642 &expected_tag[..],
643 "wycheproof XChaCha20-Poly1305 tcId={} tag mismatch",
644 test["tcId"]
645 );
646
647 let mut buf2 = expected_ct.clone();
648 cipher
649 .decrypt_in_place(&mut buf2, &nonce, &aad, &expected_tag)
650 .expect("wycheproof XChaCha20-Poly1305 decrypt failed");
651 assert_eq!(buf2, pt, "wycheproof XChaCha20-Poly1305 tcId={} pt mismatch", test["tcId"]);
652 valid_tested += 1;
653 } else {
654 let mut buf = expected_ct.clone();
655 let res = cipher.decrypt_in_place(&mut buf, &nonce, &aad, &expected_tag);
656 assert!(
657 res.is_err(),
658 "wycheproof XChaCha20-Poly1305 tcId={} expected invalid but passed",
659 test["tcId"]
660 );
661 invalid_tested += 1;
662 }
663 }
664 }
665 assert!(valid_tested > 0, "no valid XChaCha20-Poly1305 wycheproof tests were run");
666 assert!(invalid_tested > 0, "no invalid XChaCha20-Poly1305 wycheproof tests were run");
667 }
668}