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.
48#[inline]
49#[must_use]
50pub fn ghash_accelerated() -> bool {
51    #[cfg(all(target_arch = "x86_64", feature = "std"))]
52    {
53        ic_core::cpu::has_pclmulqdq() && std::arch::is_x86_feature_detected!("ssse3")
54    }
55    #[cfg(not(all(target_arch = "x86_64", feature = "std")))]
56    {
57        false
58    }
59}
60
61/// Multiply `x` by `h` in GF(2^128) using the GCM bit ordering, in place.
62///
63/// Exposed within the crate so the accelerated backend can be differentially
64/// tested against it.
65pub(crate) fn portable_ghash_mul(x: &mut [u8; BLOCK_LEN], h: &[u8; BLOCK_LEN]) {
66    let mut z = [0u8; BLOCK_LEN];
67    let mut v = *h;
68    for i in 0..128 {
69        let bit = (x[i / 8] >> (7 - (i % 8))) & 1;
70        let m = bit.wrapping_neg();
71        for j in 0..BLOCK_LEN {
72            z[j] ^= v[j] & m;
73        }
74        // v >>= 1 over the whole 128-bit word, then conditionally reduce.
75        let lsb = v[BLOCK_LEN - 1] & 1;
76        let mut carry = 0u8;
77        for byte in v.iter_mut() {
78            let next = *byte & 1;
79            *byte = (*byte >> 1) | (carry << 7);
80            carry = next;
81        }
82        v[0] ^= R & lsb.wrapping_neg();
83    }
84    *x = z;
85    z.zeroize();
86    v.zeroize();
87}
88
89/// The GHASH universal hash over a sequence of 16-byte blocks.
90struct Ghash {
91    h: [u8; BLOCK_LEN],
92    /// `H^2`, `H^3`, `H^4`, for absorbing four blocks at a time.
93    ///
94    /// GHASH is a serial chain by definition -- each block's product feeds the
95    /// next -- and on a CPU where one multiply has several cycles of latency
96    /// and issues one per cycle, that chain, not the multiplier, is what bounds
97    /// AES-GCM. Expanding four steps of the recurrence removes it:
98    ///
99    /// ```text
100    /// Y' = (Y ^ X0)*H^4  ^  X1*H^3  ^  X2*H^2  ^  X3*H
101    /// ```
102    ///
103    /// Four independent multiplies where there were four dependent ones. The
104    /// identity is just distributivity over XOR in GF(2^128), and every product
105    /// here is separately reduced, so this reuses `mul` exactly as it is rather
106    /// than introducing a second reduction to get wrong.
107    powers: [[u8; BLOCK_LEN]; 3],
108    acc: [u8; BLOCK_LEN],
109    /// Whether the `PCLMULQDQ` multiply is available. Decided once per value,
110    /// from the CPU alone, so it is not a side channel.
111    ///
112    /// Only exists where an accelerated backend could be compiled in; on other
113    /// targets there is nothing to select between.
114    #[cfg(all(target_arch = "x86_64", feature = "std"))]
115    accelerated: bool,
116}
117
118impl Ghash {
119    fn new(h: [u8; BLOCK_LEN]) -> Self {
120        let mut me = Self {
121            h,
122            powers: [[0u8; BLOCK_LEN]; 3],
123            acc: [0u8; BLOCK_LEN],
124            #[cfg(all(target_arch = "x86_64", feature = "std"))]
125            accelerated: ghash_accelerated(),
126        };
127        // H^2, H^3, H^4, each built from the previous one by the same multiply
128        // the hot path uses. Once per key, off the hot path.
129        let mut p = h;
130        for slot in 0..3 {
131            me.mul_by(&mut p, &h);
132            me.powers[slot] = p;
133        }
134        me
135    }
136
137    /// `x *= y` in GCM's field, via whichever backend is live.
138    #[inline]
139    fn mul_by(&self, x: &mut [u8; BLOCK_LEN], y: &[u8; BLOCK_LEN]) {
140        #[cfg(all(target_arch = "x86_64", feature = "std"))]
141        if self.accelerated {
142            // SAFETY: `accelerated` is only true when `ghash_accelerated()`
143            // confirmed both `pclmulqdq` and `ssse3`.
144            unsafe { crate::clmul::mul(x, y) };
145            return;
146        }
147        portable_ghash_mul(x, y);
148    }
149
150    /// Absorb four whole blocks with four independent multiplies.
151    ///
152    /// Correct for the same reason the one-at-a-time path is: see `powers`.
153    #[inline]
154    fn absorb4(&mut self, blocks: &[u8]) {
155        debug_assert_eq!(blocks.len(), BLOCK_LEN * 4);
156        let mut terms = [[0u8; BLOCK_LEN]; 4];
157        for (i, t) in terms.iter_mut().enumerate() {
158            t.copy_from_slice(&blocks[i * BLOCK_LEN..(i + 1) * BLOCK_LEN]);
159        }
160        // The first term carries the accumulator in, and takes the highest
161        // power because it is the oldest.
162        for j in 0..BLOCK_LEN {
163            terms[0][j] ^= self.acc[j];
164        }
165        let multipliers = [&self.powers[2], &self.powers[1], &self.powers[0], &self.h];
166        for (t, m) in terms.iter_mut().zip(multipliers) {
167            self.mul_by(t, m);
168        }
169        self.acc = terms[0];
170        for t in &terms[1..] {
171            for j in 0..BLOCK_LEN {
172                self.acc[j] ^= t[j];
173            }
174        }
175    }
176
177    /// Multiply the accumulator by `H`, via whichever backend is live.
178    #[inline]
179    fn mul_acc(&mut self) {
180        #[cfg(all(target_arch = "x86_64", feature = "std"))]
181        if self.accelerated {
182            // SAFETY: `accelerated` is only true when `ghash_accelerated()`
183            // confirmed both `pclmulqdq` and `ssse3`.
184            unsafe { crate::clmul::mul(&mut self.acc, &self.h) };
185            return;
186        }
187        portable_ghash_mul(&mut self.acc, &self.h);
188    }
189
190    /// Absorb `data`, zero-padding the final partial block.
191    fn update_padded(&mut self, mut data: &[u8]) {
192        // Whole groups of four first; the tail falls through to the serial
193        // path, which also handles the final partial block.
194        while data.len() >= BLOCK_LEN * 4 {
195            self.absorb4(&data[..BLOCK_LEN * 4]);
196            data = &data[BLOCK_LEN * 4..];
197        }
198        for chunk in data.chunks(BLOCK_LEN) {
199            let mut block = [0u8; BLOCK_LEN];
200            block[..chunk.len()].copy_from_slice(chunk);
201            for j in 0..BLOCK_LEN {
202                self.acc[j] ^= block[j];
203            }
204            self.mul_acc();
205        }
206    }
207
208    fn finalize(self) -> [u8; BLOCK_LEN] {
209        self.acc
210    }
211}
212
213impl Drop for Ghash {
214    fn drop(&mut self) {
215        self.h.zeroize();
216        self.acc.zeroize();
217    }
218}
219
220/// Derive the initial counter block J0 from a nonce of any length.
221fn derive_j0(nonce: &[u8], h: &[u8; BLOCK_LEN]) -> [u8; BLOCK_LEN] {
222    if nonce.len() == 12 {
223        let mut j0 = [0u8; BLOCK_LEN];
224        j0[..12].copy_from_slice(nonce);
225        j0[15] = 1;
226        j0
227    } else {
228        let mut g = Ghash::new(*h);
229        g.update_padded(nonce);
230        let mut len_block = [0u8; BLOCK_LEN];
231        len_block[8..].copy_from_slice(&((nonce.len() as u64) * 8).to_be_bytes());
232        g.update_padded(&len_block);
233        g.finalize()
234    }
235}
236
237/// Shared GCM machinery over any 128-bit block cipher.
238fn gcm_core<C: BlockCipher>(
239    cipher: &C,
240    nonce: &[u8],
241    aad: &[u8],
242    in_out: &mut [u8],
243    encrypting: bool,
244) -> Result<[u8; BLOCK_LEN]> {
245    ensure!(
246        !nonce.is_empty(),
247        InvalidParameter,
248        "gcm nonce must be non-empty"
249    );
250    ensure!(
251        in_out.len() as u64 <= GcmLimits::MAX_PLAINTEXT_BYTES,
252        CounterExhausted,
253        "gcm plaintext exceeds 2^39-256 bits"
254    );
255
256    // H = E_K(0^128)
257    let mut h = [0u8; BLOCK_LEN];
258    cipher.encrypt_block(&mut h)?;
259
260    let j0 = derive_j0(nonce, &h);
261
262    // When decrypting, GHASH must run over the ciphertext, which is what
263    // `in_out` holds *before* the CTR pass.
264    let mut g = Ghash::new(h);
265    g.update_padded(aad);
266    if !encrypting {
267        g.update_padded(in_out);
268    }
269
270    // CTR starting at inc32(J0), batched so an accelerated backend can keep its
271    // pipeline full.
272    const CTR_BATCH: usize = 8;
273    let mut counter = j0;
274    increment_be32(&mut counter);
275    let mut keystream = [0u8; BLOCK_LEN * CTR_BATCH];
276    for chunk in in_out.chunks_mut(BLOCK_LEN * CTR_BATCH) {
277        let blocks = chunk.len().div_ceil(BLOCK_LEN);
278        for i in 0..blocks {
279            keystream[i * BLOCK_LEN..(i + 1) * BLOCK_LEN].copy_from_slice(&counter);
280            increment_be32(&mut counter);
281        }
282        cipher.encrypt_blocks(&mut keystream[..blocks * BLOCK_LEN])?;
283        for (d, k) in chunk.iter_mut().zip(keystream.iter()) {
284            *d ^= k;
285        }
286    }
287    keystream.zeroize();
288
289    if encrypting {
290        g.update_padded(in_out);
291    }
292
293    let mut len_block = [0u8; BLOCK_LEN];
294    len_block[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
295    len_block[8..].copy_from_slice(&((in_out.len() as u64) * 8).to_be_bytes());
296    g.update_padded(&len_block);
297
298    let mut tag = g.finalize();
299    let mut ek_j0 = j0;
300    cipher.encrypt_block(&mut ek_j0)?;
301    for j in 0..BLOCK_LEN {
302        tag[j] ^= ek_j0[j];
303    }
304    ek_j0.zeroize();
305    h.zeroize();
306    Ok(tag)
307}
308
309macro_rules! aes_gcm {
310    ($name:ident, $inner:ty, $id:literal, $disp:literal, $keylen:literal) => {
311        #[doc = concat!("SP 800-38D ", $disp, ".")]
312        pub struct $name($inner);
313
314        impl Algorithm for $name {
315            const ID: &'static str = $id;
316            const NAME: &'static str = $disp;
317        }
318
319        impl Aead for $name {
320            const KEY_LEN: usize = $keylen;
321            const NONCE_LEN: usize = 12;
322            const TAG_LEN: usize = 16;
323
324            fn new(key: &[u8]) -> Result<Self> {
325                Ok(Self(<$inner as BlockCipher>::new(key)?))
326            }
327
328            fn seal_detached(
329                &self,
330                nonce: &[u8],
331                aad: &[u8],
332                in_out: &mut [u8],
333                tag: &mut [u8],
334            ) -> Result<()> {
335                ensure!(tag.len() == 16, InvalidLength, "gcm tag buffer");
336                let t = gcm_core(&self.0, nonce, aad, in_out, true)?;
337                tag.copy_from_slice(&t);
338                Ok(())
339            }
340
341            fn open_detached(
342                &self,
343                nonce: &[u8],
344                aad: &[u8],
345                in_out: &mut [u8],
346                tag: &[u8],
347            ) -> Result<()> {
348                ensure!(tag.len() == 16, InvalidLength, "gcm tag");
349                let expected = gcm_core(&self.0, nonce, aad, in_out, false)?;
350                if ic_core::ct::verify(&expected, tag) {
351                    Ok(())
352                } else {
353                    // Never hand back unauthenticated plaintext.
354                    in_out.zeroize();
355                    Err(ic_core::err!(AuthenticationFailed, $id))
356                }
357            }
358        }
359
360        impl SelfTest for $name {
361            fn self_test() -> Result<()> {
362                let key = [0u8; $keylen];
363                let nonce = [0u8; 12];
364                let c = <Self as Aead>::new(&key)?;
365                let mut buf = [0u8; 16];
366                let mut tag = [0u8; 16];
367                c.seal_detached(&nonce, &[], &mut buf, &mut tag)?;
368                c.open_detached(&nonce, &[], &mut buf, &tag)?;
369                ensure!(buf == [0u8; 16], SelfTestFailed, $id);
370                // A flipped tag bit must be rejected.
371                tag[0] ^= 1;
372                ensure!(
373                    c.open_detached(&nonce, &[], &mut buf, &tag).is_err(),
374                    SelfTestFailed,
375                    $id
376                );
377                Ok(())
378            }
379        }
380    };
381}
382
383aes_gcm!(Aes128Gcm, Aes128, "aes-128-gcm", "AES-128-GCM", 16);
384aes_gcm!(Aes192Gcm, Aes192, "aes-192-gcm", "AES-192-GCM", 24);
385aes_gcm!(Aes256Gcm, Aes256, "aes-256-gcm", "AES-256-GCM", 32);
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390
391    /// The four-at-a-time path must agree with the one-at-a-time path.
392    ///
393    /// The specification vectors do not establish this. The longest of them is
394    /// four blocks, so they barely reach `absorb4` and never exercise it
395    /// repeatedly or alongside a tail -- a version that was wrong from the
396    /// second group onward, or wrong about the remainder, would pass all of
397    /// them. This drives both paths over every length either side of the group
398    /// boundary and compares the accumulators.
399    #[test]
400    fn the_batched_ghash_agrees_with_the_serial_one() {
401        let h = [
402            0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
403            0x2b, 0x2e,
404        ];
405
406        let mut checked = 0;
407        // Around one group, two groups, and the ragged lengths between.
408        for len in [
409            0usize, 1, 15, 16, 17, 31, 63, 64, 65, 79, 80, 127, 128, 129, 255, 256, 1024, 1025,
410        ] {
411            let data: std::vec::Vec<u8> = (0..len)
412                .map(|i| ((i as u64).wrapping_mul(0x9e37_79b9) >> 3) as u8)
413                .collect();
414
415            let mut batched = Ghash::new(h);
416            batched.update_padded(&data);
417
418            // The serial reference: one block at a time, no grouping.
419            let mut serial = Ghash::new(h);
420            for chunk in data.chunks(BLOCK_LEN) {
421                let mut block = [0u8; BLOCK_LEN];
422                block[..chunk.len()].copy_from_slice(chunk);
423                for j in 0..BLOCK_LEN {
424                    serial.acc[j] ^= block[j];
425                }
426                serial.mul_acc();
427            }
428
429            assert_eq!(
430                batched.acc, serial.acc,
431                "batched and serial GHASH disagree at {len} bytes"
432            );
433            checked += 1;
434        }
435        assert_eq!(checked, 18, "the comparison did not run");
436
437        // And the grouping must actually have been used, or the agreement
438        // above is two serial paths agreeing with each other.
439        let long = std::vec![0xa5u8; BLOCK_LEN * 4];
440        let mut g = Ghash::new(h);
441        g.absorb4(&long);
442        let mut serial = Ghash::new(h);
443        for chunk in long.chunks(BLOCK_LEN) {
444            for j in 0..BLOCK_LEN {
445                serial.acc[j] ^= chunk[j];
446            }
447            serial.mul_acc();
448        }
449        assert_eq!(g.acc, serial.acc, "absorb4 alone disagrees with four steps");
450    }
451
452    /// `H^2`, `H^3` and `H^4` must be what they claim.
453    #[test]
454    fn the_precomputed_powers_are_powers_of_h() {
455        let h = [0x3cu8; BLOCK_LEN];
456        let g = Ghash::new(h);
457        let mut expect = h;
458        for (i, stored) in g.powers.iter().enumerate() {
459            g.mul_by(&mut expect, &h);
460            assert_eq!(*stored, expect, "power {} is not H^{}", i, i + 2);
461        }
462        // Distinct, so a table of copies would fail rather than pass.
463        assert_ne!(g.powers[0], g.powers[1]);
464        assert_ne!(g.powers[1], g.powers[2]);
465        assert_ne!(g.powers[0], h);
466    }
467    use ic_core::codec::{hex, unhex};
468
469    /// Runs one of the McGrew–Viega GCM test vectors end to end.
470    fn check(key: &str, nonce: &str, pt: &str, aad: &str, ct: &str, tag: &str) {
471        let k = unhex(key).unwrap();
472        let mut buf = unhex(pt).unwrap();
473        let mut got_tag = [0u8; 16];
474        let n = unhex(nonce).unwrap();
475        let a = unhex(aad).unwrap();
476
477        match k.len() {
478            16 => {
479                let c = Aes128Gcm::new(&k).unwrap();
480                c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
481            }
482            24 => {
483                let c = Aes192Gcm::new(&k).unwrap();
484                c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
485            }
486            _ => {
487                let c = Aes256Gcm::new(&k).unwrap();
488                c.seal_detached(&n, &a, &mut buf, &mut got_tag).unwrap();
489            }
490        }
491        assert_eq!(hex(&buf), ct, "ciphertext");
492        assert_eq!(hex(&got_tag), tag, "tag");
493    }
494
495    #[test]
496    fn gcm_spec_case_1_empty() {
497        check(
498            "00000000000000000000000000000000",
499            "000000000000000000000000",
500            "",
501            "",
502            "",
503            "58e2fccefa7e3061367f1d57a4e7455a",
504        );
505    }
506
507    #[test]
508    fn gcm_spec_case_2_single_block() {
509        check(
510            "00000000000000000000000000000000",
511            "000000000000000000000000",
512            "00000000000000000000000000000000",
513            "",
514            "0388dace60b6a392f328c2b971b2fe78",
515            "ab6e47d42cec13bdf53a67b21257bddf",
516        );
517    }
518
519    /// Case 3 authenticates the full 64-byte plaintext with no AAD; case 4
520    /// below truncates it to 60 bytes and adds AAD, exercising both the
521    /// partial-block and the AAD paths through GHASH.
522    #[test]
523    fn gcm_spec_case_3_multi_block() {
524        check(
525            "feffe9928665731c6d6a8f9467308308",
526            "cafebabefacedbaddecaf888",
527            "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
528            "",
529            "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
530            "4d5c2af327cd64a62cf35abd2ba6fab4",
531        );
532    }
533
534    #[test]
535    fn gcm_spec_case_4_with_aad() {
536        check(
537            "feffe9928665731c6d6a8f9467308308",
538            "cafebabefacedbaddecaf888",
539            "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
540            "feedfacedeadbeeffeedfacedeadbeefabaddad2",
541            "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
542            "5bc94fbc3221a5db94fae95ae7121a47",
543        );
544    }
545
546    /// Case 5: a 64-bit nonce, which exercises the GHASH-based J0 derivation.
547    #[test]
548    fn gcm_short_nonce_uses_ghash_j0() {
549        check(
550            "feffe9928665731c6d6a8f9467308308",
551            "cafebabefacedbad",
552            "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
553            "feedfacedeadbeeffeedfacedeadbeefabaddad2",
554            "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
555            "3612d2e79e3b0785561be14aaca2fccb",
556        );
557    }
558
559    #[test]
560    fn aes256_gcm_vector() {
561        check(
562            "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
563            "cafebabefacedbaddecaf888",
564            "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
565            "feedfacedeadbeeffeedfacedeadbeefabaddad2",
566            "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662",
567            "76fc6ece0f4e1768cddf8853bb2d551b",
568        );
569    }
570
571    #[test]
572    fn roundtrip_and_tamper_detection() {
573        let c = Aes256Gcm::new(&[7u8; 32]).unwrap();
574        let nonce = [9u8; 12];
575        let aad = b"header";
576        let plaintext = b"attack at dawn, bring the ontology";
577
578        let mut buf = plaintext.to_vec();
579        let mut tag = [0u8; 16];
580        c.seal_detached(&nonce, aad, &mut buf, &mut tag).unwrap();
581        assert_ne!(&buf[..], &plaintext[..]);
582
583        let mut ok = buf.clone();
584        c.open_detached(&nonce, aad, &mut ok, &tag).unwrap();
585        assert_eq!(&ok[..], &plaintext[..]);
586
587        // Tampered ciphertext must fail and must not leak plaintext.
588        let mut bad = buf.clone();
589        bad[0] ^= 1;
590        assert!(c.open_detached(&nonce, aad, &mut bad, &tag).is_err());
591        assert_eq!(
592            bad,
593            vec![0u8; bad.len()],
594            "plaintext must be wiped on failure"
595        );
596
597        // Wrong AAD must fail.
598        let mut wrong_aad = buf.clone();
599        assert!(c
600            .open_detached(&nonce, b"other", &mut wrong_aad, &tag)
601            .is_err());
602
603        // Wrong nonce must fail.
604        let mut wrong_nonce = buf.clone();
605        assert!(c
606            .open_detached(&[0u8; 12], aad, &mut wrong_nonce, &tag)
607            .is_err());
608    }
609
610    #[test]
611    fn self_tests_pass() {
612        Aes128Gcm::self_test().unwrap();
613        Aes192Gcm::self_test().unwrap();
614        Aes256Gcm::self_test().unwrap();
615    }
616}