ring/aead/
aes_gcm.rs

1// Copyright 2015-2025 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15use super::{
16    aes::{self, Counter, Overlapping, OverlappingPartialBlock, BLOCK_LEN, ZERO_BLOCK},
17    gcm,
18    overlapping::IndexError,
19    Aad, Nonce, Tag,
20};
21use crate::{
22    cpu,
23    error::{self, InputTooLongError},
24    polyfill::{slice, sliceutil::overwrite_at_start, usize_from_u64_saturated},
25};
26use core::ops::RangeFrom;
27
28#[cfg(any(
29    all(target_arch = "aarch64", target_endian = "little"),
30    all(target_arch = "arm", target_endian = "little"),
31    target_arch = "x86",
32    target_arch = "x86_64"
33))]
34use cpu::GetFeature as _;
35
36mod aarch64;
37mod aeshwclmulmovbe;
38mod vaesclmulavx2;
39
40#[derive(Clone)]
41pub(super) struct Key(DynKey);
42
43impl Key {
44    pub(super) fn new(
45        key: aes::KeyBytes,
46        cpu_features: cpu::Features,
47    ) -> Result<Self, error::Unspecified> {
48        Ok(Self(DynKey::new(key, cpu_features)?))
49    }
50}
51
52#[derive(Clone)]
53enum DynKey {
54    #[cfg(target_arch = "x86_64")]
55    VAesClMulAvx2(Combo<aes::hw::Key, gcm::vclmulavx2::Key>),
56
57    #[cfg(target_arch = "x86_64")]
58    AesHwClMulAvxMovbe(Combo<aes::hw::Key, gcm::clmulavxmovbe::Key>),
59
60    #[cfg(any(
61        all(target_arch = "aarch64", target_endian = "little"),
62        target_arch = "x86",
63        target_arch = "x86_64"
64    ))]
65    AesHwClMul(Combo<aes::hw::Key, gcm::clmul::Key>),
66
67    #[cfg(any(
68        all(target_arch = "aarch64", target_endian = "little"),
69        all(target_arch = "arm", target_endian = "little")
70    ))]
71    Simd(Combo<aes::vp::Key, gcm::neon::Key>),
72
73    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
74    Simd(Combo<aes::vp::Key, gcm::fallback::Key>),
75
76    Fallback(Combo<aes::fallback::Key, gcm::fallback::Key>),
77}
78
79impl DynKey {
80    fn new(key: aes::KeyBytes, cpu: cpu::Features) -> Result<Self, error::Unspecified> {
81        let cpu = cpu.values();
82
83        #[cfg(target_arch = "x86_64")]
84        if let Some((aes, gcm)) = cpu.get_feature() {
85            // 14.3.1 Detection of VEX-Encoded AES and VPCLMULQDQ
86            let aes_key = aes::hw::Key::new(key, aes, cpu.get_feature())?;
87            let gcm_key_value = derive_gcm_key_value(&aes_key);
88            let combo = if let Some(cpu) = cpu.get_feature() {
89                let gcm_key = gcm::vclmulavx2::Key::new(gcm_key_value, cpu);
90                Self::VAesClMulAvx2(Combo { aes_key, gcm_key })
91            } else if let Some(cpu) = cpu.get_feature() {
92                let gcm_key = gcm::clmulavxmovbe::Key::new(gcm_key_value, cpu);
93                Self::AesHwClMulAvxMovbe(Combo { aes_key, gcm_key })
94            } else {
95                let gcm_key = gcm::clmul::Key::new(gcm_key_value, gcm);
96                Self::AesHwClMul(Combo { aes_key, gcm_key })
97            };
98            return Ok(combo);
99        }
100
101        // x86_64 is handled above.
102        #[cfg(any(
103            all(target_arch = "aarch64", target_endian = "little"),
104            target_arch = "x86"
105        ))]
106        if let (Some(aes), Some(gcm)) = (cpu.get_feature(), cpu.get_feature()) {
107            let aes_key = aes::hw::Key::new(key, aes, cpu.get_feature())?;
108            let gcm_key_value = derive_gcm_key_value(&aes_key);
109            let gcm_key = gcm::clmul::Key::new(gcm_key_value, gcm);
110            return Ok(Self::AesHwClMul(Combo { aes_key, gcm_key }));
111        }
112
113        #[cfg(any(
114            all(target_arch = "aarch64", target_endian = "little"),
115            all(target_arch = "arm", target_endian = "little")
116        ))]
117        if let Some(cpu) = cpu.get_feature() {
118            return Self::new_neon(key, cpu);
119        }
120
121        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
122        if let Some(cpu) = cpu.get_feature() {
123            return Self::new_ssse3(key, cpu);
124        }
125
126        let _ = cpu;
127        Self::new_fallback(key)
128    }
129
130    #[cfg(any(
131        all(target_arch = "aarch64", target_endian = "little"),
132        all(target_arch = "arm", target_endian = "little")
133    ))]
134    #[cfg_attr(target_arch = "aarch64", inline(never))]
135    fn new_neon(key: aes::KeyBytes, cpu: cpu::arm::Neon) -> Result<Self, error::Unspecified> {
136        let aes_key = aes::vp::Key::new(key, cpu)?;
137        let gcm_key_value = derive_gcm_key_value(&aes_key);
138        let gcm_key = gcm::neon::Key::new(gcm_key_value, cpu);
139        Ok(Self::Simd(Combo { aes_key, gcm_key }))
140    }
141
142    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
143    #[inline(never)]
144    fn new_ssse3(
145        key: aes::KeyBytes,
146        cpu: aes::vp::RequiredCpuFeatures,
147    ) -> Result<Self, error::Unspecified> {
148        let aes_key = aes::vp::Key::new(key, cpu)?;
149        let gcm_key_value = derive_gcm_key_value(&aes_key);
150        let gcm_key = gcm::fallback::Key::new(gcm_key_value);
151        Ok(Self::Simd(Combo { aes_key, gcm_key }))
152    }
153
154    #[cfg_attr(
155        any(
156            all(target_arch = "aarch64", target_endian = "little"),
157            all(target_arch = "arm", target_endian = "little"),
158            target_arch = "x86",
159            target_arch = "x86_64",
160        ),
161        inline(never)
162    )]
163    fn new_fallback(key: aes::KeyBytes) -> Result<Self, error::Unspecified> {
164        let aes_key = aes::fallback::Key::new(key)?;
165        let gcm_key_value = derive_gcm_key_value(&aes_key);
166        let gcm_key = gcm::fallback::Key::new(gcm_key_value);
167        Ok(Self::Fallback(Combo { aes_key, gcm_key }))
168    }
169}
170
171fn derive_gcm_key_value(aes_key: &impl aes::EncryptBlock) -> gcm::KeyValue {
172    gcm::KeyValue::new(aes_key.encrypt_block(ZERO_BLOCK))
173}
174
175const CHUNK_BLOCKS: usize = 3 * 1024 / 16;
176
177#[inline(never)]
178pub(super) fn seal(
179    Key(key): &Key,
180    nonce: Nonce,
181    aad: Aad<&[u8]>,
182    in_out: &mut [u8],
183) -> Result<Tag, error::Unspecified> {
184    let mut ctr = Counter::one(nonce);
185    let tag_iv = ctr.increment();
186
187    match key {
188        #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
189        DynKey::AesHwClMul(c) => {
190            seal_whole_partial(c, aad, in_out, ctr, tag_iv, aarch64::seal_whole)
191        }
192
193        #[cfg(target_arch = "x86_64")]
194        DynKey::VAesClMulAvx2(c) => seal_whole_partial(
195            c,
196            aad,
197            in_out,
198            ctr,
199            tag_iv,
200            vaesclmulavx2::seal_whole_vaes_clmul_avx2,
201        ),
202
203        #[cfg(target_arch = "x86_64")]
204        DynKey::AesHwClMulAvxMovbe(Combo { aes_key, gcm_key }) => {
205            aeshwclmulmovbe::seal(aes_key, gcm_key, ctr, tag_iv, aad, in_out)
206        }
207
208        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
209        DynKey::AesHwClMul(c) => seal_strided(c, aad, in_out, ctr, tag_iv),
210
211        #[cfg(any(
212            all(target_arch = "aarch64", target_endian = "little"),
213            all(target_arch = "arm", target_endian = "little"),
214            target_arch = "x86_64",
215            target_arch = "x86"
216        ))]
217        DynKey::Simd(c) => seal_strided(c, aad, in_out, ctr, tag_iv),
218
219        DynKey::Fallback(c) => seal_strided(c, aad, in_out, ctr, tag_iv),
220    }
221}
222
223#[cfg(any(
224    all(target_arch = "aarch64", target_endian = "little"),
225    target_arch = "x86_64"
226))]
227fn seal_whole_partial<A: aes::EncryptBlock, G: gcm::UpdateBlock>(
228    Combo { aes_key, gcm_key }: &Combo<A, G>,
229    aad: Aad<&[u8]>,
230    in_out: &mut [u8],
231    mut ctr: Counter,
232    tag_iv: aes::Iv,
233    seal_whole: impl FnOnce(&A, &mut gcm::Context<G>, &mut Counter, slice::AsChunksMut<u8, BLOCK_LEN>),
234) -> Result<Tag, error::Unspecified> {
235    let mut auth = gcm::Context::new(gcm_key, aad, in_out.len())?;
236    let (whole, remainder) = slice::as_chunks_mut(in_out);
237    seal_whole(aes_key, &mut auth, &mut ctr, whole);
238    let remainder = OverlappingPartialBlock::new(remainder.into())
239        .unwrap_or_else(|InputTooLongError { .. }| unreachable!());
240    seal_finish(aes_key, auth, remainder, ctr, tag_iv)
241}
242
243#[cfg_attr(
244    any(
245        all(target_arch = "aarch64", target_endian = "little"),
246        all(target_arch = "arm", target_endian = "little"),
247        target_arch = "x86",
248        target_arch = "x86_64"
249    ),
250    inline(never)
251)]
252#[cfg_attr(
253    any(
254        all(target_arch = "aarch64", target_endian = "little"),
255        target_arch = "x86_64"
256    ),
257    cold
258)]
259fn seal_strided<
260    A: aes::EncryptBlock + aes::EncryptCtr32,
261    G: gcm::UpdateBlock + gcm::UpdateBlocks,
262>(
263    Combo { aes_key, gcm_key }: &Combo<A, G>,
264    aad: Aad<&[u8]>,
265    in_out: &mut [u8],
266    mut ctr: Counter,
267    tag_iv: aes::Iv,
268) -> Result<Tag, error::Unspecified> {
269    let mut auth = gcm::Context::new(gcm_key, aad, in_out.len())?;
270
271    let (mut whole, remainder) = slice::as_chunks_mut(in_out);
272
273    for mut chunk in whole.chunks_mut::<CHUNK_BLOCKS>() {
274        aes_key.ctr32_encrypt_within(chunk.as_flattened_mut().into(), &mut ctr);
275        auth.update_blocks(chunk.as_ref());
276    }
277
278    let remainder = OverlappingPartialBlock::new(remainder.into())
279        .unwrap_or_else(|InputTooLongError { .. }| unreachable!());
280    seal_finish(aes_key, auth, remainder, ctr, tag_iv)
281}
282
283fn seal_finish<A: aes::EncryptBlock, G: gcm::UpdateBlock>(
284    aes_key: &A,
285    mut auth: gcm::Context<G>,
286    remainder: OverlappingPartialBlock<'_>,
287    ctr: Counter,
288    tag_iv: aes::Iv,
289) -> Result<Tag, error::Unspecified> {
290    let remainder_len = remainder.len();
291    if remainder_len > 0 {
292        let mut input = ZERO_BLOCK;
293        overwrite_at_start(&mut input, remainder.input());
294        let mut output = aes_key.encrypt_iv_xor_block(ctr.into(), input);
295        output[remainder_len..].fill(0);
296        auth.update_block(output);
297        remainder.overwrite_at_start(output);
298    }
299
300    Ok(finish(aes_key, auth, tag_iv))
301}
302
303#[inline(never)]
304pub(super) fn open(
305    Key(key): &Key,
306    nonce: Nonce,
307    aad: Aad<&[u8]>,
308    in_out_slice: &mut [u8],
309    src: RangeFrom<usize>,
310) -> Result<Tag, error::Unspecified> {
311    let mut ctr = Counter::one(nonce);
312    let tag_iv = ctr.increment();
313
314    match key {
315        #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
316        DynKey::AesHwClMul(c) => {
317            open_whole_partial(c, aad, in_out_slice, src, ctr, tag_iv, aarch64::open_whole)
318        }
319
320        #[cfg(target_arch = "x86_64")]
321        DynKey::VAesClMulAvx2(c) => open_whole_partial(
322            c,
323            aad,
324            in_out_slice,
325            src,
326            ctr,
327            tag_iv,
328            vaesclmulavx2::open_whole_vaes_clmul_avx2,
329        ),
330
331        #[cfg(target_arch = "x86_64")]
332        DynKey::AesHwClMulAvxMovbe(Combo { aes_key, gcm_key }) => {
333            aeshwclmulmovbe::open(aes_key, gcm_key, ctr, tag_iv, aad, in_out_slice, src)
334        }
335
336        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
337        DynKey::AesHwClMul(c) => open_strided(c, aad, in_out_slice, src, ctr, tag_iv),
338
339        #[cfg(any(
340            all(target_arch = "aarch64", target_endian = "little"),
341            all(target_arch = "arm", target_endian = "little"),
342            target_arch = "x86_64",
343            target_arch = "x86"
344        ))]
345        DynKey::Simd(c) => open_strided(c, aad, in_out_slice, src, ctr, tag_iv),
346
347        DynKey::Fallback(c) => open_strided(c, aad, in_out_slice, src, ctr, tag_iv),
348    }
349}
350
351#[cfg(any(
352    all(target_arch = "aarch64", target_endian = "little"),
353    target_arch = "x86_64"
354))]
355fn open_whole_partial<A: aes::EncryptBlock, G: gcm::UpdateBlock>(
356    Combo { aes_key, gcm_key }: &Combo<A, G>,
357    aad: Aad<&[u8]>,
358    in_out_slice: &mut [u8],
359    src: RangeFrom<usize>,
360    mut ctr: Counter,
361    tag_iv: aes::Iv,
362    open_whole: impl FnOnce(&A, &mut gcm::Context<G>, Overlapping, &mut Counter),
363) -> Result<Tag, error::Unspecified> {
364    let in_out = Overlapping::new(in_out_slice, src.clone()).map_err(error::erase::<IndexError>)?;
365    let mut auth = gcm::Context::new(gcm_key, aad, in_out.len())?;
366
367    let remainder_len = in_out.len() % BLOCK_LEN;
368
369    let in_out_slice_len = in_out_slice.len();
370    let whole_in_out_slice = &mut in_out_slice[..(in_out_slice_len - remainder_len)];
371    let whole = Overlapping::new(whole_in_out_slice, src.clone())
372        .unwrap_or_else(|IndexError { .. }| unreachable!());
373    let whole_len = whole.len();
374    open_whole(aes_key, &mut auth, whole, &mut ctr);
375
376    let remainder = &mut in_out_slice[whole_len..];
377    let remainder =
378        Overlapping::new(remainder, src).unwrap_or_else(|IndexError { .. }| unreachable!());
379    let remainder = OverlappingPartialBlock::new(remainder)
380        .unwrap_or_else(|InputTooLongError { .. }| unreachable!());
381    open_finish(aes_key, auth, remainder, ctr, tag_iv)
382}
383
384#[cfg_attr(
385    any(
386        all(
387            any(
388                all(target_arch = "aarch64", target_endian = "little"),
389                all(target_arch = "arm", target_endian = "little")
390            ),
391            target_feature = "neon"
392        ),
393        all(
394            any(target_arch = "x86", target_arch = "x86_64"),
395            target_feature = "sse"
396        )
397    ),
398    inline(never)
399)]
400#[cfg_attr(
401    any(
402        all(target_arch = "aarch64", target_endian = "little"),
403        target_arch = "x86_64"
404    ),
405    cold
406)]
407fn open_strided<
408    A: aes::EncryptBlock + aes::EncryptCtr32,
409    G: gcm::UpdateBlock + gcm::UpdateBlocks,
410>(
411    Combo { aes_key, gcm_key }: &Combo<A, G>,
412    aad: Aad<&[u8]>,
413    in_out_slice: &mut [u8],
414    src: RangeFrom<usize>,
415    mut ctr: Counter,
416    tag_iv: aes::Iv,
417) -> Result<Tag, error::Unspecified> {
418    let in_out = Overlapping::new(in_out_slice, src.clone()).map_err(error::erase::<IndexError>)?;
419    let input = in_out.input();
420    let input_len = input.len();
421
422    let mut auth = gcm::Context::new(gcm_key, aad, input_len)?;
423
424    let remainder_len = input_len % BLOCK_LEN;
425    let whole_len = input_len - remainder_len;
426    let in_prefix_len = src.start;
427
428    {
429        let mut chunk_len = CHUNK_BLOCKS * BLOCK_LEN;
430        let mut output = 0;
431        let mut input = in_prefix_len;
432        loop {
433            if whole_len - output < chunk_len {
434                chunk_len = whole_len - output;
435            }
436
437            let ciphertext = &in_out_slice[input..][..chunk_len];
438            let (ciphertext, leftover) = slice::as_chunks(ciphertext);
439            debug_assert_eq!(leftover.len(), 0);
440            if ciphertext.is_empty() {
441                break;
442            }
443            auth.update_blocks(ciphertext);
444
445            let chunk = Overlapping::new(
446                &mut in_out_slice[output..][..(chunk_len + in_prefix_len)],
447                in_prefix_len..,
448            )
449            .map_err(error::erase::<IndexError>)?;
450            aes_key.ctr32_encrypt_within(chunk, &mut ctr);
451            output += chunk_len;
452            input += chunk_len;
453        }
454    }
455
456    let in_out = Overlapping::new(&mut in_out_slice[whole_len..], src)
457        .unwrap_or_else(|IndexError { .. }| unreachable!());
458    let in_out = OverlappingPartialBlock::new(in_out)
459        .unwrap_or_else(|InputTooLongError { .. }| unreachable!());
460
461    open_finish(aes_key, auth, in_out, ctr, tag_iv)
462}
463
464fn open_finish<A: aes::EncryptBlock, G: gcm::UpdateBlock>(
465    aes_key: &A,
466    mut auth: gcm::Context<G>,
467    remainder: OverlappingPartialBlock<'_>,
468    ctr: Counter,
469    tag_iv: aes::Iv,
470) -> Result<Tag, error::Unspecified> {
471    if remainder.len() > 0 {
472        let mut input = ZERO_BLOCK;
473        overwrite_at_start(&mut input, remainder.input());
474        auth.update_block(input);
475        remainder.overwrite_at_start(aes_key.encrypt_iv_xor_block(ctr.into(), input));
476    }
477    Ok(finish(aes_key, auth, tag_iv))
478}
479
480fn finish<A: aes::EncryptBlock, G: gcm::UpdateBlock>(
481    aes_key: &A,
482    gcm_ctx: gcm::Context<G>,
483    tag_iv: aes::Iv,
484) -> Tag {
485    // Finalize the tag and return it.
486    gcm_ctx.pre_finish(|pre_tag| Tag(aes_key.encrypt_iv_xor_block(tag_iv, pre_tag)))
487}
488
489pub(super) const MAX_IN_OUT_LEN: usize = super::max_input_len(BLOCK_LEN, 2);
490
491// [NIST SP800-38D] Section 5.2.1.1. Note that [RFC 5116 Section 5.1] and
492// [RFC 5116 Section 5.2] have an off-by-one error in `P_MAX`.
493//
494// [NIST SP800-38D]:
495//    http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
496// [RFC 5116 Section 5.1]: https://tools.ietf.org/html/rfc5116#section-5.1
497// [RFC 5116 Section 5.2]: https://tools.ietf.org/html/rfc5116#section-5.2
498const _MAX_INPUT_LEN_BOUNDED_BY_NIST: () =
499    assert!(MAX_IN_OUT_LEN == usize_from_u64_saturated(((1u64 << 39) - 256) / 8));
500
501#[derive(Copy, Clone)]
502pub(super) struct Combo<Aes, Gcm> {
503    pub(super) aes_key: Aes,
504    pub(super) gcm_key: Gcm,
505}