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