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