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