Skip to main content

ic_cipher/
gcm.rs

1//! NIST SP 800-38D Galois/Counter Mode.
2//!
3//! GHASH is implemented with a branch-free bit-by-bit multiplication in
4//! GF(2^128). Table-driven GHASH is faster but indexes memory with key-derived
5//! values; the portable backend refuses that trade.
6//!
7//! # Nonce discipline
8//!
9//! Reusing a `(key, nonce)` pair under GCM is catastrophic: it leaks the
10//! authentication subkey and lets an attacker forge arbitrary messages. The
11//! ontology records this as a hard usage constraint
12//! (`nonce_reuse_consequence: "catastrophic"`) so an agent selecting GCM is
13//! told to pair it with a counter or a random 96-bit nonce under a message
14//! limit. See [`GcmLimits`].
15
16//! Indexed loops over fixed-size limb and word arrays are used throughout; they
17//! mirror the index algebra in the specifications these routines implement, so
18//! `needless_range_loop` is allowed rather than obscuring the correspondence.
19#![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
26/// The GF(2^128) reduction constant for GHASH, `x^128 + x^7 + x^2 + x + 1`.
27const R: u8 = 0xe1;
28
29/// Invocation limits an agent must respect for a single GCM key.
30///
31/// From SP 800-38D §8.3 and the AES-GCM analysis behind RFC 8446.
32pub struct GcmLimits;
33
34impl GcmLimits {
35    /// Maximum plaintext bytes in one invocation: `2^39 - 256` bits.
36    pub const MAX_PLAINTEXT_BYTES: u64 = (1 << 36) - 32;
37    /// Maximum invocations under one key with random 96-bit nonces.
38    pub const MAX_RANDOM_NONCE_INVOCATIONS: u64 = 1 << 32;
39    /// Recommended nonce length in bytes; other lengths are legal but slower
40    /// and lose the injectivity guarantee that makes counters safe.
41    pub const RECOMMENDED_NONCE_LEN: usize = 12;
42}
43
44/// Whether GHASH can use the carry-less multiply on this CPU.
45///
46/// Needs `ssse3` for the byte-reversal shuffle as well as `pclmulqdq` for the
47/// multiply itself. Detection lives in `ic-core`, like AES-NI's, so the
48/// ontology reports the same answer this dispatches on.
49#[inline]
50#[must_use]
51pub fn ghash_accelerated() -> bool {
52    ic_core::cpu::has_ghash_clmul()
53}
54
55/// Multiply `x` by `h` in GF(2^128) using the GCM bit ordering, in place.
56///
57/// Exposed within the crate so the accelerated backend can be differentially
58/// tested against it.
59pub(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        // v >>= 1 over the whole 128-bit word, then conditionally reduce.
69        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
83/// The GHASH universal hash over a sequence of 16-byte blocks.
84struct Ghash {
85    h: [u8; BLOCK_LEN],
86    /// `H`, `H^2` .. `H^8`: index `i` holds `H^(i+1)`. The first four serve
87    /// the four-block path; the rest the eight-block `PCLMULQDQ` kernel, and
88    /// are only computed once an input long enough to use them arrives.
89    ///
90    /// GHASH is a serial chain by definition -- each block's product feeds the
91    /// next -- and on a CPU where one multiply has several cycles of latency
92    /// and issues one per cycle, that chain, not the multiplier, is what bounds
93    /// AES-GCM. Expanding four steps of the recurrence removes it:
94    ///
95    /// ```text
96    /// Y' = (Y ^ X0)*H^4  ^  X1*H^3  ^  X2*H^2  ^  X3*H
97    /// ```
98    ///
99    /// Four independent multiplies where there were four dependent ones. The
100    /// identity is just distributivity over XOR in GF(2^128), and every product
101    /// here is separately reduced, so this reuses `mul` exactly as it is rather
102    /// than introducing a second reduction to get wrong.
103    powers: [[u8; BLOCK_LEN]; 8],
104    /// Whether `powers[4..]` has been computed.
105    #[cfg(all(target_arch = "x86_64", feature = "std"))]
106    high_powers: bool,
107    acc: [u8; BLOCK_LEN],
108    /// Whether the `PCLMULQDQ` multiply is available. Decided once per value,
109    /// from the CPU alone, so it is not a side channel.
110    ///
111    /// Only exists where an accelerated backend could be compiled in; on other
112    /// targets there is nothing to select between.
113    #[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        // H^2, H^3, H^4, each built from the previous one by the same multiply
129        // the hot path uses. Once per key, off the hot path.
130        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    /// `x *= y` in GCM's field, via whichever backend is live.
140    #[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            // SAFETY: `accelerated` is only true when `ghash_accelerated()`
145            // confirmed both `pclmulqdq` and `ssse3`.
146            unsafe { crate::clmul::mul(x, y) };
147            return;
148        }
149        portable_ghash_mul(x, y);
150    }
151
152    /// Absorb four whole blocks with four independent multiplies.
153    ///
154    /// Correct for the same reason the one-at-a-time path is: see `powers`.
155    #[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        // The first term carries the accumulator in, and takes the highest
163        // power because it is the oldest.
164        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    /// Multiply the accumulator by `H`, via whichever backend is live.
185    #[inline]
186    fn mul_acc(&mut self) {
187        #[cfg(all(target_arch = "x86_64", feature = "std"))]
188        if self.accelerated {
189            // SAFETY: `accelerated` is only true when `ghash_accelerated()`
190            // confirmed both `pclmulqdq` and `ssse3`.
191            unsafe { crate::clmul::mul(&mut self.acc, &self.h) };
192            return;
193        }
194        portable_ghash_mul(&mut self.acc, &self.h);
195    }
196
197    /// Absorb `data`, zero-padding the final partial block.
198    fn update_padded(&mut self, mut data: &[u8]) {
199        // Whole groups of eight through the `PCLMULQDQ` kernel, which reduces
200        // once per group; see `clmul::absorb8`.
201        #[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            // SAFETY: `accelerated` is only true when `ghash_accelerated()`
213            // confirmed both `pclmulqdq` and `ssse3`, and `whole` is a
214            // multiple of 128 bytes.
215            unsafe { crate::clmul::absorb8(&mut self.acc, &self.powers, &data[..whole]) };
216            data = &data[whole..];
217        }
218        // Then whole groups of four; the tail falls through to the serial
219        // path, which also handles the final partial block.
220        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        // Powers of `H` are as key-derived as `H` itself. These were not wiped
244        // before, when there were three of them.
245        for p in self.powers.iter_mut() {
246            p.zeroize();
247        }
248    }
249}
250
251/// GCM's counter mode: XOR `data` with the keystream from `counter`,
252/// incrementing its last 32 bits big-endian (`inc32`).
253///
254/// A trait so the AES types can take their backend's own path; see
255/// `aes::x86::ctr32_xor`. The default is the portable construction, and it is
256/// what the accelerated path is tested against.
257pub(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
263/// [`Ctr32`] through `encrypt_blocks`, eight blocks per call.
264pub(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
286/// Derive the initial counter block J0 from a nonce of any length.
287fn 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
303/// The argument checks every GCM invocation makes first, whichever path then
304/// runs it, so the fused path fails exactly where this one does.
305fn 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
319/// Shared GCM machinery over any 128-bit block cipher.
320fn 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    // H = E_K(0^128)
330    let mut h = [0u8; BLOCK_LEN];
331    cipher.encrypt_block(&mut h)?;
332
333    let j0 = derive_j0(nonce, &h);
334
335    // When decrypting, GHASH must run over the ciphertext, which is what
336    // `in_out` holds *before* the CTR pass.
337    let mut g = Ghash::new(h);
338    g.update_padded(aad);
339    if !encrypting {
340        g.update_padded(in_out);
341    }
342
343    // CTR starting at inc32(J0).
344    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/// The fused x86-64 kernel's per-key state, when this CPU can run it.
369///
370/// Built once in `new`: `H`, its powers, and which kernel. `None` where the
371/// AES backend is not AES-NI or GHASH has no carry-less multiply, and then
372/// every call takes [`gcm_core`] exactly as before.
373#[cfg(all(target_arch = "x86_64", feature = "std"))]
374type Fast = Option<crate::gcm_x86::HashKey>;
375
376/// [`Fast`] for a freshly expanded cipher, using `kernel`.
377#[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    // SAFETY: `keys` exists only on the AES-NI backend, which is only chosen
384    // after detecting `aes`, and `ghash_accelerated()` has just confirmed
385    // `pclmulqdq` and `ssse3`. `HashKey::new` checks the wide kernel's
386    // features itself.
387    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            /// The expected tag for `in_out`, sealing or opening it in place,
401            /// by the fused kernel where there is one and [`gcm_core`]
402            /// otherwise.
403            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                    // SAFETY: `hk` was built by `HashKey::new` after the
415                    // feature checks in `fast_for`, and `keys` is the AES-NI
416                    // schedule it was built from.
417                    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            /// Which fused kernel this key runs, if any.
425            #[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            /// A key forced onto `kernel`, or onto [`gcm_core`] with `None`,
431            /// so each path can be compared with the others on one CPU.
432            #[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                    // Never hand back unauthenticated plaintext.
494                    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                // A flipped tag bit must be rejected.
511                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    /// The backend's counter mode against the generic one, on the portable
532    /// cipher.
533    ///
534    /// The specification vectors are at most four blocks, so they never reach
535    /// the kernel's whole-group path more than once, never cross the 32-bit
536    /// counter wrap, and never end a message partway through a group. This
537    /// covers every length from 0 to 300 bytes at counters that wrap inside a
538    /// group, and the counter left behind.
539    #[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            // A counter far from the edge, and ones that wrap inside the last
565            // 32 bits partway through a group of eight.
566            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    /// The four-at-a-time path must agree with the one-at-a-time path.
588    ///
589    /// The specification vectors do not establish this. The longest of them is
590    /// four blocks, so they barely reach `absorb4` and never exercise it
591    /// repeatedly or alongside a tail -- a version that was wrong from the
592    /// second group onward, or wrong about the remainder, would pass all of
593    /// them. This drives both paths over every length either side of the group
594    /// boundary and compares the accumulators.
595    #[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        // Around one group, two groups, and the ragged lengths between.
604        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            // The serial reference: one block at a time, no grouping.
615            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        // The eight-block kernel with an accumulator already carrying state,
634        // as it does after the AAD: a short prefix first, then long inputs
635        // either side of a group boundary.
636        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            // Where the kernel exists it must have run, or this is the
659            // four-block path agreeing with the serial one again.
660            #[cfg(all(target_arch = "x86_64", feature = "std"))]
661            assert_eq!(batched.high_powers, batched.accelerated);
662        }
663
664        // And the grouping must actually have been used, or the agreement
665        // above is two serial paths agreeing with each other.
666        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    /// `H^2`, `H^3` and `H^4` must be what they claim.
680    #[test]
681    fn the_precomputed_powers_are_powers_of_h() {
682        let h = [0x3cu8; BLOCK_LEN];
683        let mut g = Ghash::new(h);
684        // Long enough to make the eight-block path build H^5..H^8 where it
685        // runs; elsewhere only the first four exist.
686        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        // Distinct, so a table of copies would fail rather than pass.
700        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    /// Runs one of the McGrew–Viega GCM test vectors end to end.
707    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        // And through every path this CPU has, each forced in turn, opening
732        // as well as sealing.
733        #[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    /// Case 3 authenticates the full 64-byte plaintext with no AAD; case 4
762    /// below truncates it to 60 bytes and adds AAD, exercising both the
763    /// partial-block and the AAD paths through GHASH.
764    #[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    /// Case 5: a 64-bit nonce, which exercises the GHASH-based J0 derivation.
789    #[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        // Tampered ciphertext must fail and must not leak plaintext.
830        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        // Wrong AAD must fail.
840        let mut wrong_aad = buf.clone();
841        assert!(c
842            .open_detached(&nonce, b"other", &mut wrong_aad, &tag)
843            .is_err());
844
845        // Wrong nonce must fail.
846        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/// The fused x86-64 kernels against an independent GCM.
861///
862/// The reference below is SP 800-38D §7 written out a block at a time: the
863/// portable AES (validated by the FIPS 197 vectors) and the portable GHASH
864/// multiply (validated by the GCM specification vectors), no batching, no
865/// precomputed powers, no SIMD. Every kernel this CPU can run -- and
866/// [`gcm_core`], the path every other CPU takes -- must agree with it on
867/// every message length from 0 to 600 bytes, AAD lengths across 0..=100 and
868/// beyond, every key size, nonces of several lengths, and counters that wrap
869/// inside a group.
870///
871/// The specification vectors alone would not do. The longest is four blocks,
872/// so they never reach a 128- or 256-byte group, never leave a ragged tail
873/// after one, and never put AAD and data in the same reduction.
874#[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    /// A small xorshift, so the sweep covers varied keys, nonces and data
884    /// without an RNG dependency.
885    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    /// Every path this CPU can run: `None` is [`gcm_core`].
900    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    /// The portable block cipher for `key`.
915    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    /// One serial GHASH step per 16-byte block, the last zero-padded.
933    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    /// SP 800-38D §7.1 (and §7.2 when opening) from a given `J0`: the output
948    /// and the tag.
949    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    /// `J0` from a nonce of any length, serially.
986    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    /// Seal through one path; open the result through it; open it again with
1005    /// a flipped tag bit.
1006    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                // The forced path is the one that runs, or this compares the
1017                // default path with itself.
1018                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    /// Every path against the reference on one input.
1042    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    /// The CPU's best kernel is the one `new` installs.
1068    #[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        // And forcing a narrower kernel takes it.
1086        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                // Every AAD length from 0 to 100 occurs at many message
1101                // lengths.
1102                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    /// Past what the reference can do quickly in a debug build: every path
1150    /// against [`gcm_core`].
1151    #[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    /// Nonces other than 96 bits take `J0` from GHASH, so the counter starts
1171    /// anywhere.
1172    #[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    /// The counter wraps within its last 32 bits, and must do so inside a
1187    /// group of 8 or 16 and in the tail, without carrying into the nonce.
1188    #[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                            // SAFETY: `paths()` lists only kernels whose
1223                            // features were detected, AES-NI included.
1224                            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                            // SAFETY: as above.
1231                            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    /// The specification vectors through every path, sealing and opening.
1251    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}