Skip to main content

dcrypt_algorithms/aead/gcm/
mod.rs

1//! Galois/Counter Mode (GCM) for authenticated encryption
2//!
3//! GCM is an authenticated encryption with associated data (AEAD) mode
4//! that provides both confidentiality and authenticity. It combines the
5//! Counter (CTR) mode with the GHASH authentication function.
6//!
7//! ## Implementation Note
8//!
9//! This implementation is tested against the repository's byte-bound
10//! CAVP/ACVP-format known-answer data. The local fixtures' upstream acquisition
11//! provenance is unverified; vector tests are not a FIPS validation or
12//! certification claim.
13//!
14//! ## Timing behavior
15//!
16//! Authentication tag bytes are compared with dcrypt's owned constant-time trait after
17//! checking the public length. Input lengths, state errors, and authentication
18//! results use ordinary branching. No blanket side-channel guarantee is made
19//! for every backend, compiler, or target.
20
21// Conditionally import Vec based on available features
22#[cfg(not(feature = "std"))]
23#[cfg(feature = "alloc")]
24use alloc::vec::Vec;
25
26#[cfg(feature = "std")]
27use std::vec::Vec;
28
29use dcrypt_internal::constant_time::ConstantTimeEq;
30use dcrypt_internal::random::{try_fill_bytes_zeroing_on_error, CryptoRng, RngCore};
31use dcrypt_internal::zeroing::{
32    boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
33};
34
35// Import security types from dcrypt-core - FIXED PATH
36use dcrypt_common::security::SecretBuffer;
37
38// Fix import paths by using crate:: for internal modules
39use crate::block::BlockCipher;
40use dcrypt_api::traits::symmetric::{DecryptOperation, EncryptOperation, Operation};
41use dcrypt_api::traits::AuthenticatedCipher;
42use dcrypt_api::traits::SymmetricCipher;
43
44use crate::error::{validate, Error, Result};
45use crate::types::nonce::AesGcmCompatible; // Import the AesGcmCompatible trait
46use crate::types::Nonce; // Using generic Nonce type
47use crate::types::SecretBytes;
48use dcrypt_api::error::Error as CoreError;
49use dcrypt_api::types::Ciphertext;
50
51// Import the GHASH module
52mod ghash;
53use ghash::{process_ghash, GHash};
54
55// GCM constants
56const GCM_BLOCK_SIZE: usize = 16;
57const GCM_TAG_SIZE: usize = 16;
58
59/// GCM mode implementation
60#[derive(Clone)]
61pub struct Gcm<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
62    cipher: B,
63    h: SecretBuffer<GCM_BLOCK_SIZE>, // GHASH key (encrypted all-zero block) - now secured
64    tag_len: usize,                  // desired tag length in bytes
65}
66
67impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Zeroize for Gcm<B> {
68    fn zeroize(&mut self) {
69        self.cipher.zeroize();
70        self.h.zeroize();
71        self.tag_len.zeroize();
72    }
73}
74
75impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Drop for Gcm<B> {
76    fn drop(&mut self) {
77        self.zeroize();
78    }
79}
80
81impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> ZeroizeOnDrop for Gcm<B> {}
82
83/// Key construction needed by the generic [`SymmetricCipher`] adapter.
84pub trait GcmKey: AsRef<[u8]> + AsMut<[u8]> + Clone + Zeroize {
85    /// Construct an exactly-sized block-cipher key from caller-provided bytes.
86    fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError>;
87}
88
89impl<const N: usize> GcmKey for SecretBytes<N> {
90    fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError> {
91        SecretBytes::<N>::from_slice(bytes)
92    }
93}
94
95fn gcm_block_count(data_len: usize) -> Result<usize> {
96    let num_blocks = data_len.div_ceil(GCM_BLOCK_SIZE);
97    validate::parameter(
98        (num_blocks as u128) <= u128::from(u32::MAX - 1),
99        "message_length",
100        "GCM message exceeds the 2^32-2 block construction limit",
101    )?;
102    Ok(num_blocks)
103}
104
105/// Operation for GCM encryption operations
106pub struct GcmEncryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
107    cipher: &'a Gcm<B>,
108    nonce: Option<&'a Nonce<12>>, // Using generic Nonce<12> instead of Nonce12
109    aad: Option<&'a [u8]>,
110}
111
112/// Operation for GCM decryption operations
113pub struct GcmDecryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
114    cipher: &'a Gcm<B>,
115    nonce: Option<&'a Nonce<12>>, // Using generic Nonce<12> instead of Nonce12
116    aad: Option<&'a [u8]>,
117}
118
119impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Gcm<B> {
120    /// Creates a key-only GCM instance with a fixed 16-byte tag.
121    ///
122    /// The nonce is deliberately supplied to each encrypt/decrypt operation;
123    /// retaining it in this reusable object made accidental nonce reuse easy.
124    pub fn new(cipher: B) -> Result<Self> {
125        Self::new_with_tag_len(cipher, GCM_TAG_SIZE)
126    }
127
128    /// Creates a new GCM mode instance with specified tag length (in bytes).
129    ///
130    /// Truncated tags are supported only from 12 through 16 bytes.
131    pub fn new_with_tag_len(cipher: B, tag_len: usize) -> Result<Self> {
132        // Ensure block size
133        validate::parameter(
134            B::block_size() == GCM_BLOCK_SIZE,
135            "block_size",
136            "GCM only works with 128-bit block ciphers",
137        )?;
138
139        validate::parameter(
140            (12..=GCM_TAG_SIZE).contains(&tag_len),
141            "tag_length",
142            "GCM tag length must be between 12 and 16 bytes",
143        )?;
144
145        // Generate GHASH key H (encrypt all-zero block)
146        let mut h_bytes = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
147        cipher.encrypt_block(h_bytes.as_mut())?;
148
149        // Wrap the GHASH key in SecretBuffer for secure storage
150        let h = SecretBuffer::new(*h_bytes);
151
152        Ok(Self { cipher, h, tag_len })
153    }
154
155    /// Generate initial counter value J0
156    fn generate_j0<const N: usize>(
157        &self,
158        nonce: &Nonce<N>,
159    ) -> Result<Zeroizing<[u8; GCM_BLOCK_SIZE]>>
160    where
161        Nonce<N>: AesGcmCompatible,
162    {
163        validate::parameter(
164            !nonce.is_empty() && nonce.len() <= 16,
165            "nonce_length",
166            "GCM nonce must be between 1 and 16 bytes",
167        )?;
168        let mut j0 = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
169        if nonce.len() == 12 {
170            j0[..12].copy_from_slice(nonce.as_ref());
171            j0[15] = 1;
172        } else {
173            // Convert SecretBuffer reference to array reference
174            let h_array: &[u8; GCM_BLOCK_SIZE] = self
175                .h
176                .as_ref()
177                .try_into()
178                .expect("SecretBuffer has correct size");
179
180            let mut g = GHash::new(h_array);
181            // GHash::update already pads its final partial block. Adding a
182            // second explicit padding update here produced a non-standard J0.
183            g.update(nonce.as_ref())?;
184            g.update_lengths(0, nonce.len() as u64)?;
185            j0 = g.finalize_protected();
186        }
187        Ok(j0)
188    }
189
190    /// Generate encryption keystream for CTR mode
191    fn generate_keystream(
192        &self,
193        j0: &[u8; GCM_BLOCK_SIZE],
194        data_len: usize,
195    ) -> Result<ZeroizingBytes> {
196        // Validate the construction limit before allocating output. This also
197        // makes the limit directly testable without constructing a huge slice.
198        let num_blocks = gcm_block_count(data_len)?;
199        let mut keystream = Zeroizing::new(boxed_bytes_zeroed(num_blocks * GCM_BLOCK_SIZE));
200        let mut keystream_offset = 0usize;
201
202        let mut counter = Zeroizing::new(*j0);
203        let mut ctr_val =
204            u32::from_be_bytes(counter[12..16].try_into().expect("four bytes")).wrapping_add(1);
205        counter[12..16].copy_from_slice(&ctr_val.to_be_bytes());
206
207        for _ in 0..num_blocks {
208            let mut block = Zeroizing::new(*counter);
209            self.cipher.encrypt_block(block.as_mut())?;
210            keystream[keystream_offset..keystream_offset + GCM_BLOCK_SIZE]
211                .copy_from_slice(block.as_ref());
212            keystream_offset += GCM_BLOCK_SIZE;
213            ctr_val = ctr_val.wrapping_add(1);
214            counter[12..16].copy_from_slice(&ctr_val.to_be_bytes());
215        }
216
217        Ok(keystream)
218    }
219
220    /// Generate authentication tag (full 16 bytes)
221    fn generate_tag(
222        &self,
223        j0: &[u8; GCM_BLOCK_SIZE],
224        aad: &[u8],
225        ciphertext: &[u8],
226    ) -> Result<[u8; GCM_TAG_SIZE]> {
227        // Convert SecretBuffer reference to array reference
228        let h_array: &[u8; GCM_BLOCK_SIZE] = self
229            .h
230            .as_ref()
231            .try_into()
232            .expect("SecretBuffer has correct size");
233
234        // Process the AAD and ciphertext with GHASH
235        let mut tag = process_ghash(h_array, aad, ciphertext)?;
236
237        // Encrypt the initial counter block
238        let mut j0_copy = Zeroizing::new(*j0);
239        self.cipher.encrypt_block(j0_copy.as_mut())?;
240
241        // XOR the encrypted counter with the GHASH result
242        for i in 0..GCM_TAG_SIZE {
243            tag[i] ^= j0_copy[i];
244        }
245
246        Ok(tag)
247    }
248
249    /// Internal encrypt method - exposed for testing
250    pub fn internal_encrypt<const N: usize>(
251        &self,
252        nonce: &Nonce<N>,
253        plaintext: &[u8],
254        associated_data: Option<&[u8]>,
255    ) -> Result<Vec<u8>>
256    where
257        Nonce<N>: AesGcmCompatible,
258    {
259        let aad = associated_data.unwrap_or(&[]);
260        let j0 = self.generate_j0(nonce)?;
261
262        let keystream = if plaintext.is_empty() {
263            None
264        } else {
265            Some(self.generate_keystream(&*j0, plaintext.len())?)
266        };
267        let output_len = plaintext
268            .len()
269            .checked_add(self.tag_len)
270            .ok_or(Error::Processing {
271                operation: "GCM encryption",
272                details: "ciphertext length overflow",
273            })?;
274        let mut ciphertext = Vec::with_capacity(output_len);
275        if let Some(keystream) = keystream {
276            for i in 0..plaintext.len() {
277                ciphertext.push(plaintext[i] ^ keystream[i]);
278            }
279        }
280
281        let full_tag = self.generate_tag(&*j0, aad, &ciphertext)?;
282        ciphertext.extend_from_slice(&full_tag[..self.tag_len]);
283        Ok(ciphertext)
284    }
285
286    /// Internal decrypt method; exposed for testing.
287    pub fn internal_decrypt<const N: usize>(
288        &self,
289        nonce: &Nonce<N>,
290        ciphertext: &[u8],
291        associated_data: Option<&[u8]>,
292    ) -> Result<Vec<u8>>
293    where
294        Nonce<N>: AesGcmCompatible,
295    {
296        Ok(self
297            .internal_decrypt_protected(nonce, ciphertext, associated_data)?
298            .into_inner()
299            .into_vec())
300    }
301
302    /// Decrypt into exact-size storage that clears itself on all internal
303    /// error and drop paths. Callers should use this for intermediate
304    /// plaintext that has not yet crossed a public output boundary.
305    pub fn internal_decrypt_protected<const N: usize>(
306        &self,
307        nonce: &Nonce<N>,
308        ciphertext: &[u8],
309        associated_data: Option<&[u8]>,
310    ) -> Result<ZeroizingBytes>
311    where
312        Nonce<N>: AesGcmCompatible,
313    {
314        // Length check is not a secret-dependent branch
315        validate::min_length("GCM ciphertext", ciphertext.len(), self.tag_len)?;
316
317        let aad = associated_data.unwrap_or(&[]);
318        let ciphertext_len = ciphertext.len() - self.tag_len;
319        let (ciphertext_data, received_tag) = ciphertext.split_at(ciphertext_len);
320
321        // Generate initial counter and expected tag
322        let j0 = self.generate_j0(nonce)?;
323        let full_expected = self.generate_tag(&*j0, aad, ciphertext_data)?;
324        let expected_tag = &full_expected[..self.tag_len];
325
326        // Generate keystream and decrypt data
327        let keystream = self.generate_keystream(&*j0, ciphertext_len)?;
328        let mut plaintext = Zeroizing::new(boxed_bytes_zeroed(ciphertext_len));
329        for i in 0..ciphertext_len {
330            plaintext[i] = ciphertext_data[i] ^ keystream[i];
331        }
332
333        // Compare all tag bytes without a value-dependent early exit.
334        let tag_matches = expected_tag.ct_eq(received_tag);
335
336        // The tag bytes are compared without a value-dependent early exit. The
337        // public result/error branch is not a blanket constant-time claim for
338        // the complete decrypt operation.
339        if tag_matches.unwrap_u8() == 0 {
340            Err(Error::Authentication { algorithm: "GCM" })
341        } else {
342            Ok(plaintext)
343        }
344    }
345}
346
347// Implement the marker trait AuthenticatedCipher
348impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> AuthenticatedCipher for Gcm<B> {
349    const TAG_SIZE: usize = GCM_TAG_SIZE;
350    const ALGORITHM_ID: &'static str = "GCM";
351}
352
353// Implement SymmetricCipher trait
354impl<B> SymmetricCipher for Gcm<B>
355where
356    B: BlockCipher + Zeroize + ZeroizeOnDrop,
357    B::Key: GcmKey,
358{
359    type Key = B::Key;
360    type Nonce = Nonce<12>; // Using generic Nonce<12> instead of Nonce12
361    type Ciphertext = Ciphertext;
362    type EncryptOperation<'a>
363        = GcmEncryptOperation<'a, B>
364    where
365        Self: 'a;
366    type DecryptOperation<'a>
367        = GcmDecryptOperation<'a, B>
368    where
369        Self: 'a;
370
371    fn name() -> &'static str {
372        "GCM"
373    }
374
375    fn encrypt(&self) -> <Self as SymmetricCipher>::EncryptOperation<'_> {
376        GcmEncryptOperation {
377            cipher: self,
378            nonce: None,
379            aad: None,
380        }
381    }
382
383    fn decrypt(&self) -> <Self as SymmetricCipher>::DecryptOperation<'_> {
384        GcmDecryptOperation {
385            cipher: self,
386            nonce: None,
387            aad: None,
388        }
389    }
390
391    fn generate_key<R: RngCore + CryptoRng>(
392        rng: &mut R,
393    ) -> core::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
394        B::generate_key(rng).map_err(CoreError::from)
395    }
396
397    fn generate_nonce<R: RngCore + CryptoRng>(
398        rng: &mut R,
399    ) -> core::result::Result<<Self as SymmetricCipher>::Nonce, CoreError> {
400        let mut nonce_data = [0u8; 12];
401        try_fill_bytes_zeroing_on_error(rng, &mut nonce_data).map_err(|_| CoreError::Other {
402            context: "randomness",
403            #[cfg(feature = "std")]
404            message: "caller-provided randomness source failed".to_string(),
405        })?;
406        Ok(Nonce::<12>::new(nonce_data)) // Using generic Nonce::<12> instead of Nonce12
407    }
408
409    fn derive_key_from_bytes(
410        bytes: &[u8],
411    ) -> core::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
412        if bytes.len() != B::key_size() {
413            return Err(CoreError::InvalidLength {
414                context: "GCM key derivation",
415                expected: B::key_size(),
416                actual: bytes.len(),
417            });
418        }
419        B::Key::from_key_bytes(bytes)
420    }
421}
422
423// Implement Operation for GcmEncryptOperation
424impl<B> Operation<Ciphertext> for GcmEncryptOperation<'_, B>
425where
426    B: BlockCipher + Zeroize + ZeroizeOnDrop,
427    B::Key: GcmKey,
428{
429    fn execute(self) -> core::result::Result<Ciphertext, CoreError> {
430        let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
431            context: "GCM encryption",
432            #[cfg(feature = "std")]
433            message: "Nonce is required for GCM encryption".to_string(),
434        })?;
435        let plaintext = b""; // Default empty plaintext
436
437        let ciphertext = self
438            .cipher
439            .internal_encrypt(nonce, plaintext, self.aad)
440            .map_err(CoreError::from)?;
441
442        Ok(Ciphertext::new(ciphertext))
443    }
444}
445
446// Implement EncryptOperation for GcmEncryptOperation
447impl<'a, B> EncryptOperation<'a, Gcm<B>> for GcmEncryptOperation<'a, B>
448where
449    B: BlockCipher + Zeroize + ZeroizeOnDrop,
450    B::Key: GcmKey,
451{
452    fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
453        self.nonce = Some(nonce);
454        self
455    }
456
457    fn with_aad(mut self, aad: &'a [u8]) -> Self {
458        self.aad = Some(aad);
459        self
460    }
461
462    fn encrypt(self, plaintext: &'a [u8]) -> core::result::Result<Ciphertext, CoreError> {
463        let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
464            context: "GCM encryption",
465            #[cfg(feature = "std")]
466            message: "Nonce is required for GCM encryption".to_string(),
467        })?;
468
469        let ciphertext = self
470            .cipher
471            .internal_encrypt(nonce, plaintext, self.aad)
472            .map_err(CoreError::from)?;
473
474        Ok(Ciphertext::new(ciphertext))
475    }
476}
477
478// Implement Operation for GcmDecryptOperation
479impl<B> Operation<Vec<u8>> for GcmDecryptOperation<'_, B>
480where
481    B: BlockCipher + Zeroize + ZeroizeOnDrop,
482    B::Key: GcmKey,
483{
484    fn execute(self) -> core::result::Result<Vec<u8>, CoreError> {
485        Err(CoreError::InvalidParameter {
486            context: "GCM decryption",
487            #[cfg(feature = "std")]
488            message: "Use decrypt method instead".to_string(),
489        })
490    }
491}
492
493// Implement DecryptOperation for GcmDecryptOperation
494impl<'a, B> DecryptOperation<'a, Gcm<B>> for GcmDecryptOperation<'a, B>
495where
496    B: BlockCipher + Zeroize + ZeroizeOnDrop,
497    B::Key: GcmKey,
498{
499    fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
500        self.nonce = Some(nonce);
501        self
502    }
503
504    fn with_aad(mut self, aad: &'a [u8]) -> Self {
505        self.aad = Some(aad);
506        self
507    }
508
509    fn decrypt(
510        self,
511        ciphertext: &'a <Gcm<B> as SymmetricCipher>::Ciphertext,
512    ) -> core::result::Result<Vec<u8>, CoreError> {
513        let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
514            context: "GCM decryption",
515            #[cfg(feature = "std")]
516            message: "Nonce is required for GCM decryption".to_string(),
517        })?;
518
519        self.cipher
520            .internal_decrypt(nonce, ciphertext.as_ref(), self.aad)
521            .map_err(CoreError::from)
522    }
523}
524
525#[cfg(test)]
526mod tests;