1#![allow(clippy::needless_range_loop)]
20
21use crate::aes::{Aes128, Aes192, Aes256, BLOCK_LEN};
22use crate::modes::increment_be32;
23use ic_core::traits::{Aead, Algorithm, BlockCipher, SelfTest};
24use ic_core::{ensure, Result, Zeroize};
25
26const R: u8 = 0xe1;
28
29pub struct GcmLimits;
33
34impl GcmLimits {
35 pub const MAX_PLAINTEXT_BYTES: u64 = (1 << 36) - 32;
37 pub const MAX_RANDOM_NONCE_INVOCATIONS: u64 = 1 << 32;
39 pub const RECOMMENDED_NONCE_LEN: usize = 12;
42}
43
44#[inline]
50#[must_use]
51pub fn ghash_accelerated() -> bool {
52 ic_core::cpu::has_ghash_clmul()
53}
54
55pub(crate) fn portable_ghash_mul(x: &mut [u8; BLOCK_LEN], h: &[u8; BLOCK_LEN]) {
60 let mut z = [0u8; BLOCK_LEN];
61 let mut v = *h;
62 for i in 0..128 {
63 let bit = (x[i / 8] >> (7 - (i % 8))) & 1;
64 let m = bit.wrapping_neg();
65 for j in 0..BLOCK_LEN {
66 z[j] ^= v[j] & m;
67 }
68 let lsb = v[BLOCK_LEN - 1] & 1;
70 let mut carry = 0u8;
71 for byte in v.iter_mut() {
72 let next = *byte & 1;
73 *byte = (*byte >> 1) | (carry << 7);
74 carry = next;
75 }
76 v[0] ^= R & lsb.wrapping_neg();
77 }
78 *x = z;
79 z.zeroize();
80 v.zeroize();
81}
82
83struct Ghash {
85 h: [u8; BLOCK_LEN],
86 powers: [[u8; BLOCK_LEN]; 8],
104 #[cfg(all(target_arch = "x86_64", feature = "std"))]
106 high_powers: bool,
107 acc: [u8; BLOCK_LEN],
108 #[cfg(all(target_arch = "x86_64", feature = "std"))]
114 accelerated: bool,
115}
116
117impl Ghash {
118 fn new(h: [u8; BLOCK_LEN]) -> Self {
119 let mut me = Self {
120 h,
121 powers: [[0u8; BLOCK_LEN]; 8],
122 #[cfg(all(target_arch = "x86_64", feature = "std"))]
123 high_powers: false,
124 acc: [0u8; BLOCK_LEN],
125 #[cfg(all(target_arch = "x86_64", feature = "std"))]
126 accelerated: ghash_accelerated(),
127 };
128 me.powers[0] = h;
131 for slot in 1..4 {
132 let mut p = me.powers[slot - 1];
133 me.mul_by(&mut p, &h);
134 me.powers[slot] = p;
135 }
136 me
137 }
138
139 #[inline]
141 fn mul_by(&self, x: &mut [u8; BLOCK_LEN], y: &[u8; BLOCK_LEN]) {
142 #[cfg(all(target_arch = "x86_64", feature = "std"))]
143 if self.accelerated {
144 unsafe { crate::clmul::mul(x, y) };
147 return;
148 }
149 portable_ghash_mul(x, y);
150 }
151
152 #[inline]
156 fn absorb4(&mut self, blocks: &[u8]) {
157 debug_assert_eq!(blocks.len(), BLOCK_LEN * 4);
158 let mut terms = [[0u8; BLOCK_LEN]; 4];
159 for (i, t) in terms.iter_mut().enumerate() {
160 t.copy_from_slice(&blocks[i * BLOCK_LEN..(i + 1) * BLOCK_LEN]);
161 }
162 for j in 0..BLOCK_LEN {
165 terms[0][j] ^= self.acc[j];
166 }
167 let multipliers = [
168 &self.powers[3],
169 &self.powers[2],
170 &self.powers[1],
171 &self.powers[0],
172 ];
173 for (t, m) in terms.iter_mut().zip(multipliers) {
174 self.mul_by(t, m);
175 }
176 self.acc = terms[0];
177 for t in &terms[1..] {
178 for j in 0..BLOCK_LEN {
179 self.acc[j] ^= t[j];
180 }
181 }
182 }
183
184 #[inline]
186 fn mul_acc(&mut self) {
187 #[cfg(all(target_arch = "x86_64", feature = "std"))]
188 if self.accelerated {
189 unsafe { crate::clmul::mul(&mut self.acc, &self.h) };
192 return;
193 }
194 portable_ghash_mul(&mut self.acc, &self.h);
195 }
196
197 fn update_padded(&mut self, mut data: &[u8]) {
199 #[cfg(all(target_arch = "x86_64", feature = "std"))]
202 if self.accelerated && data.len() >= BLOCK_LEN * 8 {
203 if !self.high_powers {
204 for slot in 4..8 {
205 let mut p = self.powers[slot - 1];
206 self.mul_by(&mut p, &self.h);
207 self.powers[slot] = p;
208 }
209 self.high_powers = true;
210 }
211 let whole = data.len() - data.len() % (BLOCK_LEN * 8);
212 unsafe { crate::clmul::absorb8(&mut self.acc, &self.powers, &data[..whole]) };
216 data = &data[whole..];
217 }
218 while data.len() >= BLOCK_LEN * 4 {
221 self.absorb4(&data[..BLOCK_LEN * 4]);
222 data = &data[BLOCK_LEN * 4..];
223 }
224 for chunk in data.chunks(BLOCK_LEN) {
225 let mut block = [0u8; BLOCK_LEN];
226 block[..chunk.len()].copy_from_slice(chunk);
227 for j in 0..BLOCK_LEN {
228 self.acc[j] ^= block[j];
229 }
230 self.mul_acc();
231 }
232 }
233
234 fn finalize(self) -> [u8; BLOCK_LEN] {
235 self.acc
236 }
237}
238
239impl Drop for Ghash {
240 fn drop(&mut self) {
241 self.h.zeroize();
242 self.acc.zeroize();
243 for p in self.powers.iter_mut() {
246 p.zeroize();
247 }
248 }
249}
250
251pub(crate) trait Ctr32: BlockCipher {
258 fn ctr32_xor(&self, counter: &mut [u8; BLOCK_LEN], data: &mut [u8]) -> Result<()> {
259 ctr32_xor_generic(self, counter, data)
260 }
261}
262
263pub(crate) fn ctr32_xor_generic<C: BlockCipher + ?Sized>(
265 cipher: &C,
266 counter: &mut [u8; BLOCK_LEN],
267 data: &mut [u8],
268) -> Result<()> {
269 const CTR_BATCH: usize = 8;
270 let mut keystream = [0u8; BLOCK_LEN * CTR_BATCH];
271 for chunk in data.chunks_mut(BLOCK_LEN * CTR_BATCH) {
272 let blocks = chunk.len().div_ceil(BLOCK_LEN);
273 for i in 0..blocks {
274 keystream[i * BLOCK_LEN..(i + 1) * BLOCK_LEN].copy_from_slice(counter);
275 increment_be32(counter);
276 }
277 cipher.encrypt_blocks(&mut keystream[..blocks * BLOCK_LEN])?;
278 for (d, k) in chunk.iter_mut().zip(keystream.iter()) {
279 *d ^= k;
280 }
281 }
282 keystream.zeroize();
283 Ok(())
284}
285
286fn derive_j0(nonce: &[u8], h: &[u8; BLOCK_LEN]) -> [u8; BLOCK_LEN] {
288 if nonce.len() == 12 {
289 let mut j0 = [0u8; BLOCK_LEN];
290 j0[..12].copy_from_slice(nonce);
291 j0[15] = 1;
292 j0
293 } else {
294 let mut g = Ghash::new(*h);
295 g.update_padded(nonce);
296 let mut len_block = [0u8; BLOCK_LEN];
297 len_block[8..].copy_from_slice(&((nonce.len() as u64) * 8).to_be_bytes());
298 g.update_padded(&len_block);
299 g.finalize()
300 }
301}
302
303fn check_invocation(nonce: &[u8], in_out: &[u8]) -> Result<()> {
306 ensure!(
307 !nonce.is_empty(),
308 InvalidParameter,
309 "gcm nonce must be non-empty"
310 );
311 ensure!(
312 in_out.len() as u64 <= GcmLimits::MAX_PLAINTEXT_BYTES,
313 CounterExhausted,
314 "gcm plaintext exceeds 2^39-256 bits"
315 );
316 Ok(())
317}
318
319fn gcm_core<C: Ctr32>(
321 cipher: &C,
322 nonce: &[u8],
323 aad: &[u8],
324 in_out: &mut [u8],
325 encrypting: bool,
326) -> Result<[u8; BLOCK_LEN]> {
327 check_invocation(nonce, in_out)?;
328
329 let mut h = [0u8; BLOCK_LEN];
331 cipher.encrypt_block(&mut h)?;
332
333 let j0 = derive_j0(nonce, &h);
334
335 let mut g = Ghash::new(h);
338 g.update_padded(aad);
339 if !encrypting {
340 g.update_padded(in_out);
341 }
342
343 let mut counter = j0;
345 increment_be32(&mut counter);
346 cipher.ctr32_xor(&mut counter, in_out)?;
347
348 if encrypting {
349 g.update_padded(in_out);
350 }
351
352 let mut len_block = [0u8; BLOCK_LEN];
353 len_block[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
354 len_block[8..].copy_from_slice(&((in_out.len() as u64) * 8).to_be_bytes());
355 g.update_padded(&len_block);
356
357 let mut tag = g.finalize();
358 let mut ek_j0 = j0;
359 cipher.encrypt_block(&mut ek_j0)?;
360 for j in 0..BLOCK_LEN {
361 tag[j] ^= ek_j0[j];
362 }
363 ek_j0.zeroize();
364 h.zeroize();
365 Ok(tag)
366}
367
368#[cfg(all(target_arch = "x86_64", feature = "std"))]
374type Fast = Option<crate::gcm_x86::HashKey>;
375
376#[cfg(all(target_arch = "x86_64", feature = "std"))]
378fn fast_for(keys: Option<&crate::aes::x86::Keys>, kernel: crate::gcm_x86::Kernel) -> Fast {
379 let keys = keys?;
380 if !ghash_accelerated() {
381 return None;
382 }
383 Some(unsafe { crate::gcm_x86::HashKey::new(keys, kernel) })
388}
389
390macro_rules! aes_gcm {
391 ($name:ident, $inner:ty, $id:literal, $disp:literal, $keylen:literal) => {
392 #[doc = concat!("SP 800-38D ", $disp, ".")]
393 pub struct $name {
394 cipher: $inner,
395 #[cfg(all(target_arch = "x86_64", feature = "std"))]
396 fast: Fast,
397 }
398
399 impl $name {
400 fn run(
404 &self,
405 nonce: &[u8],
406 aad: &[u8],
407 in_out: &mut [u8],
408 encrypting: bool,
409 ) -> Result<[u8; BLOCK_LEN]> {
410 #[cfg(all(target_arch = "x86_64", feature = "std"))]
411 if let (Some(hk), Some(keys)) = (&self.fast, self.cipher.aesni_keys()) {
412 check_invocation(nonce, in_out)?;
413 let j0 = derive_j0(nonce, hk.h());
414 return Ok(unsafe {
418 crate::gcm_x86::seal_or_open(keys, hk, &j0, aad, in_out, encrypting)
419 });
420 }
421 gcm_core(&self.cipher, nonce, aad, in_out, encrypting)
422 }
423
424 #[cfg(all(test, target_arch = "x86_64", feature = "std"))]
426 fn kernel(&self) -> Option<crate::gcm_x86::Kernel> {
427 self.fast.as_ref().map(|hk| hk.kernel())
428 }
429
430 #[cfg(all(test, target_arch = "x86_64", feature = "std"))]
433 fn with_kernel(key: &[u8], kernel: Option<crate::gcm_x86::Kernel>) -> Result<Self> {
434 ic_core::module::operational()?;
435 let cipher = <$inner as BlockCipher>::new(key)?;
436 let fast = match kernel {
437 Some(k) => fast_for(cipher.aesni_keys(), k),
438 None => None,
439 };
440 Ok(Self { cipher, fast })
441 }
442 }
443
444 impl Algorithm for $name {
445 const ID: &'static str = $id;
446 const NAME: &'static str = $disp;
447 }
448
449 impl Aead for $name {
450 const KEY_LEN: usize = $keylen;
451 const NONCE_LEN: usize = 12;
452 const TAG_LEN: usize = 16;
453
454 fn new(key: &[u8]) -> Result<Self> {
455 ic_core::module::operational()?;
456 let cipher = <$inner as BlockCipher>::new(key)?;
457 #[cfg(all(target_arch = "x86_64", feature = "std"))]
458 let fast = fast_for(cipher.aesni_keys(), crate::gcm_x86::best_kernel());
459 Ok(Self {
460 cipher,
461 #[cfg(all(target_arch = "x86_64", feature = "std"))]
462 fast,
463 })
464 }
465
466 fn seal_detached(
467 &self,
468 nonce: &[u8],
469 aad: &[u8],
470 in_out: &mut [u8],
471 tag: &mut [u8],
472 ) -> Result<()> {
473 ic_core::module::operational()?;
474 ensure!(tag.len() == 16, InvalidLength, "gcm tag buffer");
475 let t = self.run(nonce, aad, in_out, true)?;
476 tag.copy_from_slice(&t);
477 Ok(())
478 }
479
480 fn open_detached(
481 &self,
482 nonce: &[u8],
483 aad: &[u8],
484 in_out: &mut [u8],
485 tag: &[u8],
486 ) -> Result<()> {
487 ic_core::module::operational()?;
488 ensure!(tag.len() == 16, InvalidLength, "gcm tag");
489 let expected = self.run(nonce, aad, in_out, false)?;
490 if ic_core::ct::verify(&expected, tag) {
491 Ok(())
492 } else {
493 in_out.zeroize();
495 Err(ic_core::err!(AuthenticationFailed, $id))
496 }
497 }
498 }
499
500 impl SelfTest for $name {
501 fn self_test() -> Result<()> {
502 let key = [0u8; $keylen];
503 let nonce = [0u8; 12];
504 let c = <Self as Aead>::new(&key)?;
505 let mut buf = [0u8; 16];
506 let mut tag = [0u8; 16];
507 c.seal_detached(&nonce, &[], &mut buf, &mut tag)?;
508 c.open_detached(&nonce, &[], &mut buf, &tag)?;
509 ensure!(buf == [0u8; 16], SelfTestFailed, $id);
510 tag[0] ^= 1;
512 ensure!(
513 c.open_detached(&nonce, &[], &mut buf, &tag).is_err(),
514 SelfTestFailed,
515 $id
516 );
517 Ok(())
518 }
519 }
520 };
521}
522
523aes_gcm!(Aes128Gcm, Aes128, "aes-128-gcm", "AES-128-GCM", 16);
524aes_gcm!(Aes192Gcm, Aes192, "aes-192-gcm", "AES-192-GCM", 24);
525aes_gcm!(Aes256Gcm, Aes256, "aes-256-gcm", "AES-256-GCM", 32);
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
540 fn the_backend_counter_mode_agrees_with_the_generic_one() {
541 use crate::aes::{Aes128, Aes256, Backend};
542 type CtrFn = std::boxed::Box<dyn Fn(&mut [u8; 16], &mut [u8])>;
543 let mut checked = 0;
544 for key_len in [16usize, 32] {
545 let key: std::vec::Vec<u8> = (0..key_len).map(|i| (i * 7 + 3) as u8).collect();
546 let (fast, slow): (CtrFn, CtrFn) = if key_len == 16 {
547 let f = Aes128::new(&key).unwrap();
548 let s = Aes128::new_portable(&key).unwrap();
549 if crate::aes::aesni_available() {
550 assert_eq!(f.backend(), Backend::Aesni);
551 }
552 (
553 std::boxed::Box::new(move |c, d| f.ctr32_xor(c, d).unwrap()),
554 std::boxed::Box::new(move |c, d| ctr32_xor_generic(&s, c, d).unwrap()),
555 )
556 } else {
557 let f = Aes256::new(&key).unwrap();
558 let s = Aes256::new_portable(&key).unwrap();
559 (
560 std::boxed::Box::new(move |c, d| f.ctr32_xor(c, d).unwrap()),
561 std::boxed::Box::new(move |c, d| ctr32_xor_generic(&s, c, d).unwrap()),
562 )
563 };
564 for tail in [0x0000_0001u32, 0xffff_fffd, 0xffff_fff9, 0xffff_ffff] {
567 for len in 0..=300usize {
568 let mut counter = [0x5au8; 16];
569 counter[12..].copy_from_slice(&tail.to_be_bytes());
570 let data: std::vec::Vec<u8> = (0..len).map(|i| (i * 13) as u8).collect();
571 let (mut a, mut b) = (data.clone(), data.clone());
572 let (mut ca, mut cb) = (counter, counter);
573 fast(&mut ca, &mut a);
574 slow(&mut cb, &mut b);
575 assert_eq!(a, b, "key {key_len}, counter tail {tail:#x}, {len} bytes");
576 assert_eq!(
577 ca, cb,
578 "final counter, key {key_len}, tail {tail:#x}, {len} bytes"
579 );
580 checked += 1;
581 }
582 }
583 }
584 assert_eq!(checked, 2 * 4 * 301);
585 }
586
587 #[test]
596 fn the_batched_ghash_agrees_with_the_serial_one() {
597 let h = [
598 0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
599 0x2b, 0x2e,
600 ];
601
602 let mut checked = 0;
603 for len in [
605 0usize, 1, 15, 16, 17, 31, 63, 64, 65, 79, 80, 127, 128, 129, 255, 256, 1024, 1025,
606 ] {
607 let data: std::vec::Vec<u8> = (0..len)
608 .map(|i| ((i as u64).wrapping_mul(0x9e37_79b9) >> 3) as u8)
609 .collect();
610
611 let mut batched = Ghash::new(h);
612 batched.update_padded(&data);
613
614 let mut serial = Ghash::new(h);
616 for chunk in data.chunks(BLOCK_LEN) {
617 let mut block = [0u8; BLOCK_LEN];
618 block[..chunk.len()].copy_from_slice(chunk);
619 for j in 0..BLOCK_LEN {
620 serial.acc[j] ^= block[j];
621 }
622 serial.mul_acc();
623 }
624
625 assert_eq!(
626 batched.acc, serial.acc,
627 "batched and serial GHASH disagree at {len} bytes"
628 );
629 checked += 1;
630 }
631 assert_eq!(checked, 18, "the comparison did not run");
632
633 for (prefix, len) in [(17usize, 128usize), (16, 2048), (5, 2048 + 48), (33, 1023)] {
637 let data: std::vec::Vec<u8> = (0..prefix + len)
638 .map(|i| ((i as u64).wrapping_mul(0x2545_f491_4f6c_dd1d) >> 11) as u8)
639 .collect();
640 let (head, tail) = data.split_at(prefix);
641 let mut batched = Ghash::new(h);
642 batched.update_padded(head);
643 batched.update_padded(tail);
644
645 let mut serial = Ghash::new(h);
646 for part in [head, tail] {
647 for chunk in part.chunks(BLOCK_LEN) {
648 let mut block = [0u8; BLOCK_LEN];
649 block[..chunk.len()].copy_from_slice(chunk);
650 for j in 0..BLOCK_LEN {
651 serial.acc[j] ^= block[j];
652 }
653 serial.mul_acc();
654 }
655 }
656 assert_eq!(batched.acc, serial.acc, "prefix {prefix}, then {len} bytes");
657
658 #[cfg(all(target_arch = "x86_64", feature = "std"))]
661 assert_eq!(batched.high_powers, batched.accelerated);
662 }
663
664 let long = std::vec![0xa5u8; BLOCK_LEN * 4];
667 let mut g = Ghash::new(h);
668 g.absorb4(&long);
669 let mut serial = Ghash::new(h);
670 for chunk in long.chunks(BLOCK_LEN) {
671 for j in 0..BLOCK_LEN {
672 serial.acc[j] ^= chunk[j];
673 }
674 serial.mul_acc();
675 }
676 assert_eq!(g.acc, serial.acc, "absorb4 alone disagrees with four steps");
677 }
678
679 #[test]
681 fn the_precomputed_powers_are_powers_of_h() {
682 let h = [0x3cu8; BLOCK_LEN];
683 let mut g = Ghash::new(h);
684 g.update_padded(&[0u8; BLOCK_LEN * 8]);
687 #[cfg(all(target_arch = "x86_64", feature = "std"))]
688 let built = if g.high_powers { 8 } else { 4 };
689 #[cfg(not(all(target_arch = "x86_64", feature = "std")))]
690 let built = 4;
691
692 let mut expect = h;
693 for (i, stored) in g.powers[..built].iter().enumerate() {
694 if i > 0 {
695 g.mul_by(&mut expect, &h);
696 }
697 assert_eq!(*stored, expect, "powers[{i}] is not H^{}", i + 1);
698 }
699 for i in 1..built {
701 assert_ne!(g.powers[i - 1], g.powers[i]);
702 }
703 }
704 use ic_core::codec::{hex, unhex};
705
706 fn check(key: &str, nonce: &str, pt: &str, aad: &str, ct: &str, tag: &str) {
708 let k = unhex(key).unwrap();
709 let mut buf = unhex(pt).unwrap();
710 let mut got_tag = [0u8; 16];
711 let n = unhex(nonce).unwrap();
712 let a = unhex(aad).unwrap();
713
714 match k.len() {
715 16 => {
716 let c = Aes128Gcm::new(&k).unwrap();
717 c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
718 }
719 24 => {
720 let c = Aes192Gcm::new(&k).unwrap();
721 c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
722 }
723 _ => {
724 let c = Aes256Gcm::new(&k).unwrap();
725 c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
726 }
727 }
728 assert_eq!(hex(&buf), ct, "ciphertext");
729 assert_eq!(hex(&got_tag), tag, "tag");
730
731 #[cfg(all(target_arch = "x86_64", feature = "std"))]
734 super::fused_tests::check_every_path(&k, &n, &a, &unhex(pt).unwrap(), ct, tag);
735 }
736
737 #[test]
738 fn gcm_spec_case_1_empty() {
739 check(
740 "00000000000000000000000000000000",
741 "000000000000000000000000",
742 "",
743 "",
744 "",
745 "58e2fccefa7e3061367f1d57a4e7455a",
746 );
747 }
748
749 #[test]
750 fn gcm_spec_case_2_single_block() {
751 check(
752 "00000000000000000000000000000000",
753 "000000000000000000000000",
754 "00000000000000000000000000000000",
755 "",
756 "0388dace60b6a392f328c2b971b2fe78",
757 "ab6e47d42cec13bdf53a67b21257bddf",
758 );
759 }
760
761 #[test]
765 fn gcm_spec_case_3_multi_block() {
766 check(
767 "feffe9928665731c6d6a8f9467308308",
768 "cafebabefacedbaddecaf888",
769 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
770 "",
771 "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
772 "4d5c2af327cd64a62cf35abd2ba6fab4",
773 );
774 }
775
776 #[test]
777 fn gcm_spec_case_4_with_aad() {
778 check(
779 "feffe9928665731c6d6a8f9467308308",
780 "cafebabefacedbaddecaf888",
781 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
782 "feedfacedeadbeeffeedfacedeadbeefabaddad2",
783 "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
784 "5bc94fbc3221a5db94fae95ae7121a47",
785 );
786 }
787
788 #[test]
790 fn gcm_short_nonce_uses_ghash_j0() {
791 check(
792 "feffe9928665731c6d6a8f9467308308",
793 "cafebabefacedbad",
794 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
795 "feedfacedeadbeeffeedfacedeadbeefabaddad2",
796 "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
797 "3612d2e79e3b0785561be14aaca2fccb",
798 );
799 }
800
801 #[test]
802 fn aes256_gcm_vector() {
803 check(
804 "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
805 "cafebabefacedbaddecaf888",
806 "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
807 "feedfacedeadbeeffeedfacedeadbeefabaddad2",
808 "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662",
809 "76fc6ece0f4e1768cddf8853bb2d551b",
810 );
811 }
812
813 #[test]
814 fn roundtrip_and_tamper_detection() {
815 let c = Aes256Gcm::new(&[7u8; 32]).unwrap();
816 let nonce = [9u8; 12];
817 let aad = b"header";
818 let plaintext = b"attack at dawn, bring the ontology";
819
820 let mut buf = plaintext.to_vec();
821 let mut tag = [0u8; 16];
822 c.seal_detached(&nonce, aad, &mut buf, &mut tag).unwrap();
823 assert_ne!(&buf[..], &plaintext[..]);
824
825 let mut ok = buf.clone();
826 c.open_detached(&nonce, aad, &mut ok, &tag).unwrap();
827 assert_eq!(&ok[..], &plaintext[..]);
828
829 let mut bad = buf.clone();
831 bad[0] ^= 1;
832 assert!(c.open_detached(&nonce, aad, &mut bad, &tag).is_err());
833 assert_eq!(
834 bad,
835 vec![0u8; bad.len()],
836 "plaintext must be wiped on failure"
837 );
838
839 let mut wrong_aad = buf.clone();
841 assert!(c
842 .open_detached(&nonce, b"other", &mut wrong_aad, &tag)
843 .is_err());
844
845 let mut wrong_nonce = buf.clone();
847 assert!(c
848 .open_detached(&[0u8; 12], aad, &mut wrong_nonce, &tag)
849 .is_err());
850 }
851
852 #[test]
853 fn self_tests_pass() {
854 Aes128Gcm::self_test().unwrap();
855 Aes192Gcm::self_test().unwrap();
856 Aes256Gcm::self_test().unwrap();
857 }
858}
859
860#[cfg(all(test, target_arch = "x86_64", feature = "std"))]
875mod fused_tests {
876 use super::*;
877 use crate::gcm_x86::{self, best_kernel, Kernel, Wide, Wide512};
878 use std::boxed::Box;
879 use std::vec::Vec;
880
881 type Block = [u8; BLOCK_LEN];
882
883 struct Xs(u64);
886
887 impl Xs {
888 fn next(&mut self) -> u64 {
889 self.0 ^= self.0 << 13;
890 self.0 ^= self.0 >> 7;
891 self.0 ^= self.0 << 17;
892 self.0
893 }
894 fn bytes(&mut self, n: usize) -> Vec<u8> {
895 (0..n).map(|_| (self.next() >> 24) as u8).collect()
896 }
897 }
898
899 fn paths() -> Vec<Option<Kernel>> {
901 let mut v = std::vec![None];
902 if crate::aes::aesni_available() && ghash_accelerated() {
903 v.push(Some(Kernel::Clmul8));
904 if Wide::detect().is_some() {
905 v.push(Some(Kernel::Vaes256));
906 }
907 if Wide512::detect().is_some() {
908 v.push(Some(Kernel::Vaes512));
909 }
910 }
911 v
912 }
913
914 fn portable(key: &[u8]) -> Box<dyn Fn(&mut Block)> {
916 match key.len() {
917 16 => {
918 let c = crate::aes::Aes128::new_portable(key).unwrap();
919 Box::new(move |b| c.encrypt_block(b).unwrap())
920 }
921 24 => {
922 let c = crate::aes::Aes192::new_portable(key).unwrap();
923 Box::new(move |b| c.encrypt_block(b).unwrap())
924 }
925 _ => {
926 let c = crate::aes::Aes256::new_portable(key).unwrap();
927 Box::new(move |b| c.encrypt_block(b).unwrap())
928 }
929 }
930 }
931
932 fn ghash_serial(h: &Block, y: &mut Block, bytes: &[u8]) {
934 for chunk in bytes.chunks(BLOCK_LEN) {
935 for (a, b) in y.iter_mut().zip(chunk) {
936 *a ^= b;
937 }
938 portable_ghash_mul(y, h);
939 }
940 }
941
942 fn inc32(c: &mut Block) {
943 let n = u32::from_be_bytes([c[12], c[13], c[14], c[15]]).wrapping_add(1);
944 c[12..].copy_from_slice(&n.to_be_bytes());
945 }
946
947 fn reference_j0(
950 key: &[u8],
951 j0: Block,
952 aad: &[u8],
953 data: &[u8],
954 sealing: bool,
955 ) -> (Vec<u8>, Block) {
956 let e = portable(key);
957 let mut h = [0u8; BLOCK_LEN];
958 e(&mut h);
959 let mut out = data.to_vec();
960 let mut ctr = j0;
961 for chunk in out.chunks_mut(BLOCK_LEN) {
962 inc32(&mut ctr);
963 let mut ks = ctr;
964 e(&mut ks);
965 for (d, k) in chunk.iter_mut().zip(ks.iter()) {
966 *d ^= k;
967 }
968 }
969 let ciphertext = if sealing { &out[..] } else { data };
970 let mut y = [0u8; BLOCK_LEN];
971 ghash_serial(&h, &mut y, aad);
972 ghash_serial(&h, &mut y, ciphertext);
973 let mut len = [0u8; BLOCK_LEN];
974 len[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
975 len[8..].copy_from_slice(&((data.len() as u64) * 8).to_be_bytes());
976 ghash_serial(&h, &mut y, &len);
977 let mut mask = j0;
978 e(&mut mask);
979 for (t, m) in y.iter_mut().zip(mask.iter()) {
980 *t ^= m;
981 }
982 (out, y)
983 }
984
985 fn reference_j0_of(key: &[u8], nonce: &[u8]) -> Block {
987 if nonce.len() == 12 {
988 let mut j0 = [0u8; BLOCK_LEN];
989 j0[..12].copy_from_slice(nonce);
990 j0[15] = 1;
991 return j0;
992 }
993 let e = portable(key);
994 let mut h = [0u8; BLOCK_LEN];
995 e(&mut h);
996 let mut y = [0u8; BLOCK_LEN];
997 ghash_serial(&h, &mut y, nonce);
998 let mut len = [0u8; BLOCK_LEN];
999 len[8..].copy_from_slice(&((nonce.len() as u64) * 8).to_be_bytes());
1000 ghash_serial(&h, &mut y, &len);
1001 y
1002 }
1003
1004 fn through(
1007 key: &[u8],
1008 path: Option<Kernel>,
1009 nonce: &[u8],
1010 aad: &[u8],
1011 pt: &[u8],
1012 ) -> (Vec<u8>, Block, Vec<u8>, bool) {
1013 macro_rules! go {
1014 ($ty:ty) => {{
1015 let c = <$ty>::with_kernel(key, path).unwrap();
1016 assert_eq!(c.kernel(), path, "{} did not take {path:?}", <$ty>::ID);
1019 let mut ct = pt.to_vec();
1020 let mut tag = [0u8; BLOCK_LEN];
1021 c.seal_detached(nonce, aad, &mut ct, &mut tag).unwrap();
1022 let mut opened = ct.clone();
1023 c.open_detached(nonce, aad, &mut opened, &tag).unwrap();
1024 let mut bad_tag = tag;
1025 bad_tag[(pt.len() + aad.len()) % BLOCK_LEN] ^= 0x01;
1026 let mut tampered = ct.clone();
1027 let refused = c
1028 .open_detached(nonce, aad, &mut tampered, &bad_tag)
1029 .is_err()
1030 && tampered.iter().all(|&b| b == 0);
1031 (ct, tag, opened, refused)
1032 }};
1033 }
1034 match key.len() {
1035 16 => go!(Aes128Gcm),
1036 24 => go!(Aes192Gcm),
1037 _ => go!(Aes256Gcm),
1038 }
1039 }
1040
1041 fn agree(key: &[u8], nonce: &[u8], aad: &[u8], pt: &[u8]) -> usize {
1043 let j0 = reference_j0_of(key, nonce);
1044 let (want_ct, want_tag) = reference_j0(key, j0, aad, pt, true);
1045 let mut n = 0;
1046 for path in paths() {
1047 let (ct, tag, opened, refused) = through(key, path, nonce, aad, pt);
1048 let what = std::format!(
1049 "{path:?}, key {} bytes, nonce {} bytes, aad {} bytes, {} bytes",
1050 key.len(),
1051 nonce.len(),
1052 aad.len(),
1053 pt.len()
1054 );
1055 assert_eq!(ct, want_ct, "ciphertext: {what}");
1056 assert_eq!(tag, want_tag, "tag: {what}");
1057 assert_eq!(opened, pt, "opened: {what}");
1058 assert!(
1059 refused,
1060 "a flipped tag bit was accepted, or left plaintext: {what}"
1061 );
1062 n += 1;
1063 }
1064 n
1065 }
1066
1067 #[test]
1069 fn new_selects_the_best_kernel_this_cpu_has() {
1070 let c = Aes256Gcm::new(&[1u8; 32]).unwrap();
1071 if crate::aes::aesni_available() && ghash_accelerated() {
1072 assert_eq!(c.kernel(), Some(best_kernel()));
1073 let expect = if Wide512::detect().is_some() {
1074 Kernel::Vaes512
1075 } else if Wide::detect().is_some() {
1076 Kernel::Vaes256
1077 } else {
1078 Kernel::Clmul8
1079 };
1080 assert_eq!(best_kernel(), expect);
1081 std::eprintln!("AES-GCM kernel on this CPU: {:?}", best_kernel());
1082 } else {
1083 assert_eq!(c.kernel(), None);
1084 }
1085 if c.kernel().is_some() {
1087 let n = Aes128Gcm::with_kernel(&[1u8; 16], Some(Kernel::Clmul8)).unwrap();
1088 assert_eq!(n.kernel(), Some(Kernel::Clmul8));
1089 }
1090 }
1091
1092 #[test]
1093 fn every_path_matches_the_reference_at_every_length_to_600() {
1094 let mut rng = Xs(0x9e37_79b9_7f4a_7c15);
1095 let mut checked = 0;
1096 for key_len in [16usize, 24, 32] {
1097 for len in 0..=600usize {
1098 let key = rng.bytes(key_len);
1099 let nonce = rng.bytes(12);
1100 let aad = rng.bytes((len * 37 + key_len) % 101);
1103 let pt = rng.bytes(len);
1104 checked += agree(&key, &nonce, &aad, &pt);
1105 }
1106 }
1107 assert!(checked >= 3 * 601 * 2, "only {checked} comparisons ran");
1108 }
1109
1110 #[test]
1111 fn every_path_matches_the_reference_for_every_aad_length_to_100() {
1112 let mut rng = Xs(0x2545_f491_4f6c_dd1d);
1113 for key_len in [16usize, 32] {
1114 for len in [
1115 0usize, 1, 15, 16, 17, 127, 128, 129, 255, 256, 257, 383, 513,
1116 ] {
1117 for aad_len in 0..=100usize {
1118 let key = rng.bytes(key_len);
1119 let nonce = rng.bytes(12);
1120 let aad = rng.bytes(aad_len);
1121 let pt = rng.bytes(len);
1122 agree(&key, &nonce, &aad, &pt);
1123 }
1124 }
1125 }
1126 }
1127
1128 #[test]
1129 fn every_path_matches_the_reference_on_large_inputs() {
1130 let mut rng = Xs(0xd1b5_4a32_d192_ed03);
1131 for (aad_len, len) in [
1132 (1000usize, 0usize),
1133 (4099, 1),
1134 (16, 4096),
1135 (13, 4096 + 255),
1136 (300, 65536 + 17),
1137 (0, 3 * 256 + 128 + 15),
1138 ] {
1139 for key_len in [16usize, 24, 32] {
1140 let key = rng.bytes(key_len);
1141 let nonce = rng.bytes(12);
1142 let aad = rng.bytes(aad_len);
1143 let pt = rng.bytes(len);
1144 agree(&key, &nonce, &aad, &pt);
1145 }
1146 }
1147 }
1148
1149 #[test]
1152 fn every_path_matches_gcm_core_on_a_megabyte() {
1153 let mut rng = Xs(0x6a09_e667_f3bc_c909);
1154 for (key_len, len) in [(16usize, (1 << 20) + 13), (32, (1 << 20) + 256 + 7)] {
1155 let key = rng.bytes(key_len);
1156 let nonce = rng.bytes(12);
1157 let aad = rng.bytes(77);
1158 let pt = rng.bytes(len);
1159 let (want_ct, want_tag, _, _) = through(&key, None, &nonce, &aad, &pt);
1160 for path in paths().into_iter().skip(1) {
1161 let (ct, tag, opened, refused) = through(&key, path, &nonce, &aad, &pt);
1162 assert!(ct == want_ct, "{path:?}: ciphertext differs at {len} bytes");
1163 assert_eq!(tag, want_tag, "{path:?}: tag at {len} bytes");
1164 assert!(opened == pt, "{path:?}: open at {len} bytes");
1165 assert!(refused, "{path:?}: tampered tag at {len} bytes");
1166 }
1167 }
1168 }
1169
1170 #[test]
1173 fn every_path_matches_the_reference_with_other_nonce_lengths() {
1174 let mut rng = Xs(0xbb67_ae85_84ca_a73b);
1175 for nonce_len in [1usize, 8, 11, 13, 16, 60, 128] {
1176 for len in [0usize, 5, 64, 200, 256, 300, 600] {
1177 let key = rng.bytes(32);
1178 let nonce = rng.bytes(nonce_len);
1179 let aad = rng.bytes(len % 37);
1180 let pt = rng.bytes(len);
1181 agree(&key, &nonce, &aad, &pt);
1182 }
1183 }
1184 }
1185
1186 #[test]
1189 fn every_kernel_wraps_the_counter_as_inc32_does() {
1190 let mut rng = Xs(0x3c6e_f372_fe94_f82b);
1191 let mut checked = 0;
1192 for kernel in paths().into_iter().flatten() {
1193 for key_len in [16usize, 32] {
1194 let key = rng.bytes(key_len);
1195 macro_rules! keys {
1196 ($ty:ty) => {{
1197 <$ty as BlockCipher>::new(&key).unwrap()
1198 }};
1199 }
1200 for back in [1u32, 2, 3, 7, 8, 9, 15, 16, 17, 31] {
1201 for len in [
1202 16usize,
1203 100,
1204 128,
1205 255,
1206 256,
1207 257,
1208 512,
1209 600,
1210 1072,
1211 2048 + 256 + 128 + 33,
1212 ] {
1213 let mut j0 = [0xa5u8; BLOCK_LEN];
1214 j0[12..].copy_from_slice(&0u32.wrapping_sub(back).to_be_bytes());
1215 let aad = rng.bytes(len % 23);
1216 let pt = rng.bytes(len);
1217 let (want_ct, want_tag) = reference_j0(&key, j0, &aad, &pt, true);
1218 let mut got = pt.clone();
1219 let tag = if key_len == 16 {
1220 let c = keys!(crate::aes::Aes128);
1221 let keys = c.aesni_keys().unwrap();
1222 let hk = unsafe { gcm_x86::HashKey::new(keys, kernel) };
1225 assert_eq!(hk.kernel(), kernel);
1226 unsafe { gcm_x86::seal_or_open(keys, &hk, &j0, &aad, &mut got, true) }
1227 } else {
1228 let c = keys!(crate::aes::Aes256);
1229 let keys = c.aesni_keys().unwrap();
1230 let hk = unsafe { gcm_x86::HashKey::new(keys, kernel) };
1232 assert_eq!(hk.kernel(), kernel);
1233 unsafe { gcm_x86::seal_or_open(keys, &hk, &j0, &aad, &mut got, true) }
1234 };
1235 let what = std::format!(
1236 "{kernel:?}, key {key_len}, wrap {back} back, {len} bytes"
1237 );
1238 assert_eq!(got, want_ct, "ciphertext: {what}");
1239 assert_eq!(tag, want_tag, "tag: {what}");
1240 checked += 1;
1241 }
1242 }
1243 }
1244 }
1245 if crate::aes::aesni_available() && ghash_accelerated() {
1246 assert!(checked >= 2 * 10 * 10, "only {checked} wrap cases ran");
1247 }
1248 }
1249
1250 pub(super) fn check_every_path(
1252 key: &[u8],
1253 nonce: &[u8],
1254 aad: &[u8],
1255 pt: &[u8],
1256 ct: &str,
1257 tag: &str,
1258 ) {
1259 use ic_core::codec::hex;
1260 for path in paths() {
1261 let (got_ct, got_tag, opened, refused) = through(key, path, nonce, aad, pt);
1262 assert_eq!(hex(&got_ct), ct, "{path:?} ciphertext");
1263 assert_eq!(hex(&got_tag), tag, "{path:?} tag");
1264 assert_eq!(opened, pt, "{path:?} open");
1265 assert!(refused, "{path:?} accepted a bad tag");
1266 }
1267 }
1268}