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 has been validated against official NIST Cryptographic Algorithm
10//! Validation Program (CAVP) test vectors. It follows the Galois/Counter Mode (GCM)
11//! specification as defined in NIST Special Publication 800-38D.
12//!
13//! ## Constant-Time Guarantees
14//!
15//! This implementation is designed to be timing-attack resistant:
16//! - All cryptographic operations are performed before authentication validation
17//! - Authentication tag verification uses the `subtle` crate's constant-time comparison
18//! - Timing-safe conditional operations are performed without data-dependent branches
19//! - Memory barriers prevent compiler optimizations that could introduce timing variation
20
21#![cfg_attr(not(feature = "std"), no_std)]
22
23// Conditionally import Vec based on available features
24#[cfg(not(feature = "std"))]
25#[cfg(feature = "alloc")]
26use alloc::vec::Vec;
27
28#[cfg(feature = "std")]
29use std::vec::Vec;
30
31use byteorder::{BigEndian, ByteOrder};
32#[cfg(not(feature = "std"))]
33use portable_atomic::{compiler_fence, Ordering};
34#[cfg(feature = "std")]
35use std::sync::atomic::{compiler_fence, Ordering};
36use subtle::ConstantTimeEq;
37use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
38
39// Import security types from dcrypt-core - FIXED PATH
40use dcrypt_common::security::{SecretBuffer, SecureZeroingType};
41
42// Fix import paths by using crate:: for internal modules
43use crate::block::BlockCipher;
44use dcrypt_api::traits::symmetric::{DecryptOperation, EncryptOperation, Operation};
45use dcrypt_api::traits::AuthenticatedCipher;
46use dcrypt_api::traits::SymmetricCipher;
47
48use crate::error::{validate, Error, Result};
49use crate::types::nonce::AesGcmCompatible; // Import the AesGcmCompatible trait
50use crate::types::Nonce; // Using generic Nonce type
51use crate::types::SecretBytes;
52use dcrypt_api::error::Error as CoreError;
53use dcrypt_api::types::Ciphertext;
54
55// Import the GHASH module
56mod ghash;
57use ghash::{process_ghash, GHash};
58
59// GCM constants
60const GCM_BLOCK_SIZE: usize = 16;
61const GCM_TAG_SIZE: usize = 16;
62
63/// GCM mode implementation
64#[derive(Clone, Zeroize, ZeroizeOnDrop)]
65pub struct Gcm<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
66    cipher: B,
67    h: SecretBuffer<GCM_BLOCK_SIZE>, // GHASH key (encrypted all-zero block) - now secured
68    nonce: Zeroizing<Vec<u8>>,
69    tag_len: usize, // desired tag length in bytes
70}
71
72/// Operation for GCM encryption operations
73pub struct GcmEncryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
74    cipher: &'a Gcm<B>,
75    nonce: Option<&'a Nonce<12>>, // Using generic Nonce<12> instead of Nonce12
76    aad: Option<&'a [u8]>,
77}
78
79/// Operation for GCM decryption operations
80pub struct GcmDecryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
81    cipher: &'a Gcm<B>,
82    nonce: Option<&'a Nonce<12>>, // Using generic Nonce<12> instead of Nonce12
83    aad: Option<&'a [u8]>,
84}
85
86impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Gcm<B> {
87    /// Creates a new GCM mode instance with default (16-byte) tag.
88    pub fn new<const N: usize>(cipher: B, nonce: &Nonce<N>) -> Result<Self>
89    where
90        Nonce<N>: AesGcmCompatible,
91    {
92        Self::new_with_tag_len(cipher, nonce, GCM_TAG_SIZE)
93    }
94
95    /// Creates a new GCM mode instance with specified tag length (in bytes).
96    ///
97    /// tag_len must be between 1 and 16 (inclusive).
98    pub fn new_with_tag_len<const N: usize>(
99        cipher: B,
100        nonce: &Nonce<N>,
101        tag_len: usize,
102    ) -> Result<Self>
103    where
104        Nonce<N>: AesGcmCompatible,
105    {
106        // Ensure block size
107        validate::parameter(
108            B::block_size() == GCM_BLOCK_SIZE,
109            "block_size",
110            "GCM only works with 128-bit block ciphers",
111        )?;
112
113        validate::parameter(
114            !nonce.is_empty() && nonce.len() <= 16,
115            "nonce_length",
116            "GCM nonce must be between 1 and 16 bytes",
117        )?;
118
119        validate::parameter(
120            (1..=GCM_TAG_SIZE).contains(&tag_len),
121            "tag_length",
122            "GCM tag length must be between 1 and 16 bytes",
123        )?;
124
125        // Generate GHASH key H (encrypt all-zero block)
126        let mut h_bytes = [0u8; GCM_BLOCK_SIZE];
127        cipher.encrypt_block(&mut h_bytes)?;
128
129        // Wrap the GHASH key in SecretBuffer for secure storage
130        let h = SecretBuffer::new(h_bytes);
131
132        Ok(Self {
133            cipher,
134            h,
135            nonce: Zeroizing::new(nonce.as_ref().to_vec()),
136            tag_len,
137        })
138    }
139
140    /// Generate initial counter value J0
141    fn generate_j0(&self) -> Result<[u8; GCM_BLOCK_SIZE]> {
142        let mut j0 = [0u8; GCM_BLOCK_SIZE];
143        if self.nonce.len() == 12 {
144            j0[..12].copy_from_slice(&self.nonce);
145            j0[15] = 1;
146        } else {
147            // Convert SecretBuffer reference to array reference
148            let h_array: &[u8; GCM_BLOCK_SIZE] = self
149                .h
150                .as_ref()
151                .try_into()
152                .expect("SecretBuffer has correct size");
153
154            let mut g = GHash::new(h_array);
155            g.update(&self.nonce)?;
156            let rem = self.nonce.len() % GCM_BLOCK_SIZE;
157            if rem != 0 {
158                g.update(&vec![0u8; GCM_BLOCK_SIZE - rem])?;
159            }
160            g.update_lengths(0, self.nonce.len() as u64)?;
161            j0 = g.finalize();
162        }
163        Ok(j0)
164    }
165
166    /// Generate encryption keystream for CTR mode
167    fn generate_keystream(
168        &self,
169        j0: &[u8; GCM_BLOCK_SIZE],
170        data_len: usize,
171    ) -> Result<Zeroizing<Vec<u8>>> {
172        let num_blocks = data_len.div_ceil(GCM_BLOCK_SIZE);
173        let mut keystream = Zeroizing::new(Vec::with_capacity(num_blocks * GCM_BLOCK_SIZE));
174
175        let mut counter = *j0;
176        let mut ctr_val = BigEndian::read_u32(&counter[12..16]).wrapping_add(1);
177        BigEndian::write_u32(&mut counter[12..16], ctr_val);
178
179        for _ in 0..num_blocks {
180            let mut block = counter;
181            self.cipher.encrypt_block(&mut block)?;
182            keystream.extend_from_slice(&block);
183            ctr_val = ctr_val.wrapping_add(1);
184            BigEndian::write_u32(&mut counter[12..16], ctr_val);
185        }
186
187        Ok(keystream)
188    }
189
190    /// Generate authentication tag (full 16 bytes)
191    fn generate_tag(
192        &self,
193        j0: &[u8; GCM_BLOCK_SIZE],
194        aad: &[u8],
195        ciphertext: &[u8],
196    ) -> Result<[u8; GCM_TAG_SIZE]> {
197        // Convert SecretBuffer reference to array reference
198        let h_array: &[u8; GCM_BLOCK_SIZE] = self
199            .h
200            .as_ref()
201            .try_into()
202            .expect("SecretBuffer has correct size");
203
204        // Process the AAD and ciphertext with GHASH
205        let mut tag = process_ghash(h_array, aad, ciphertext)?;
206
207        // Encrypt the initial counter block
208        let mut j0_copy = *j0;
209        self.cipher.encrypt_block(&mut j0_copy)?;
210
211        // XOR the encrypted counter with the GHASH result
212        for i in 0..GCM_TAG_SIZE {
213            tag[i] ^= j0_copy[i];
214        }
215
216        Ok(tag)
217    }
218
219    /// Internal encrypt method - exposed for testing
220    pub fn internal_encrypt(
221        &self,
222        plaintext: &[u8],
223        associated_data: Option<&[u8]>,
224    ) -> Result<Vec<u8>> {
225        let aad = associated_data.unwrap_or(&[]);
226        let j0 = self.generate_j0()?;
227
228        let mut ciphertext = Vec::with_capacity(plaintext.len() + self.tag_len);
229        if !plaintext.is_empty() {
230            let keystream = self.generate_keystream(&j0, plaintext.len())?;
231            for i in 0..plaintext.len() {
232                ciphertext.push(plaintext[i] ^ keystream[i]);
233            }
234        }
235
236        let full_tag = self.generate_tag(&j0, aad, &ciphertext)?;
237        ciphertext.extend_from_slice(&full_tag[..self.tag_len]);
238        Ok(ciphertext)
239    }
240
241    /// Internal decrypt method with improved constant-time implementation - exposed for testing
242    pub fn internal_decrypt(
243        &self,
244        ciphertext: &[u8],
245        associated_data: Option<&[u8]>,
246    ) -> Result<Vec<u8>> {
247        // Length check is not a secret-dependent branch
248        validate::min_length("GCM ciphertext", ciphertext.len(), self.tag_len)?;
249
250        let aad = associated_data.unwrap_or(&[]);
251        let ciphertext_len = ciphertext.len() - self.tag_len;
252        let (ciphertext_data, received_tag) = ciphertext.split_at(ciphertext_len);
253
254        // Generate initial counter and expected tag
255        let j0 = self.generate_j0()?;
256        let full_expected = self.generate_tag(&j0, aad, ciphertext_data)?;
257        let expected_tag = &full_expected[..self.tag_len];
258
259        // Generate keystream and decrypt data
260        let keystream = self.generate_keystream(&j0, ciphertext_len)?;
261        let mut plaintext = Zeroizing::new(Vec::with_capacity(ciphertext_len));
262        for i in 0..ciphertext_len {
263            plaintext.push(ciphertext_data[i] ^ keystream[i]);
264        }
265
266        // Memory barrier to ensure all decryption operations complete before comparison
267        compiler_fence(Ordering::SeqCst);
268
269        // Constant-time tag comparison that doesn't leak timing information
270        let tag_matches = expected_tag.ct_eq(received_tag);
271
272        // Memory barrier to ensure comparison is done before selecting result
273        compiler_fence(Ordering::SeqCst);
274
275        // Convert the constant-time comparison result to an error if needed
276        if tag_matches.unwrap_u8() == 0 {
277            // Zeroize the plaintext securely before returning to avoid leaking data
278            // This runs on the error path, but doesn't leak timing information about the tag
279            // since all cryptographic work is already done by this point
280            Err(Error::Authentication { algorithm: "GCM" })
281        } else {
282            Ok(plaintext.to_vec())
283        }
284    }
285}
286
287// Implement SecureZeroingType for Gcm
288impl<B: BlockCipher + Clone + Zeroize + ZeroizeOnDrop> SecureZeroingType for Gcm<B> {
289    fn zeroed() -> Self {
290        // This is a bit tricky since we need a cipher instance
291        // For now, we'll panic if called, as this shouldn't be used directly
292        panic!("Cannot create a zeroed GCM instance without a cipher")
293    }
294
295    fn secure_clone(&self) -> Self {
296        self.clone()
297    }
298}
299
300// Implement the marker trait AuthenticatedCipher
301impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> AuthenticatedCipher for Gcm<B> {
302    const TAG_SIZE: usize = GCM_TAG_SIZE;
303    const ALGORITHM_ID: &'static str = "GCM";
304}
305
306// Implement SymmetricCipher trait
307impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> SymmetricCipher for Gcm<B> {
308    // We can't use B::KEY_SIZE in const generic expressions, so we'll use a different approach
309    type Key = SecretBytes<32>; // Using a fixed size for demonstration - adjust based on your needs
310    type Nonce = Nonce<12>; // Using generic Nonce<12> instead of Nonce12
311    type Ciphertext = Ciphertext;
312    type EncryptOperation<'a>
313        = GcmEncryptOperation<'a, B>
314    where
315        Self: 'a;
316    type DecryptOperation<'a>
317        = GcmDecryptOperation<'a, B>
318    where
319        Self: 'a;
320
321    fn name() -> &'static str {
322        "GCM"
323    }
324
325    fn encrypt(&self) -> <Self as SymmetricCipher>::EncryptOperation<'_> {
326        GcmEncryptOperation {
327            cipher: self,
328            nonce: None,
329            aad: None,
330        }
331    }
332
333    fn decrypt(&self) -> <Self as SymmetricCipher>::DecryptOperation<'_> {
334        GcmDecryptOperation {
335            cipher: self,
336            nonce: None,
337            aad: None,
338        }
339    }
340
341    fn generate_key<R: rand::RngCore + rand::CryptoRng>(
342        rng: &mut R,
343    ) -> std::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
344        let mut key_data = [0u8; 32]; // Using same fixed size as type Key
345        rng.fill_bytes(&mut key_data);
346        Ok(SecretBytes::new(key_data))
347    }
348
349    fn generate_nonce<R: rand::RngCore + rand::CryptoRng>(
350        rng: &mut R,
351    ) -> std::result::Result<<Self as SymmetricCipher>::Nonce, CoreError> {
352        let mut nonce_data = [0u8; 12];
353        rng.fill_bytes(&mut nonce_data);
354        Ok(Nonce::<12>::new(nonce_data)) // Using generic Nonce::<12> instead of Nonce12
355    }
356
357    fn derive_key_from_bytes(
358        bytes: &[u8],
359    ) -> std::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
360        if bytes.len() < 32 {
361            // Using same fixed size as type Key
362            return Err(CoreError::InvalidLength {
363                context: "GCM key derivation",
364                expected: 32,
365                actual: bytes.len(),
366            });
367        }
368
369        let mut key_data = [0u8; 32]; // Using same fixed size as type Key
370        key_data.copy_from_slice(&bytes[..32]);
371        Ok(SecretBytes::new(key_data))
372    }
373}
374
375// Implement Operation for GcmEncryptOperation
376impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Operation<Ciphertext>
377    for GcmEncryptOperation<'_, B>
378{
379    fn execute(self) -> std::result::Result<Ciphertext, CoreError> {
380        if self.nonce.is_none() {
381            return Err(CoreError::InvalidParameter {
382                context: "GCM encryption",
383                #[cfg(feature = "std")]
384                message: "Nonce is required for GCM encryption".to_string(),
385            });
386        }
387        let plaintext = b""; // Default empty plaintext
388
389        let ciphertext = self
390            .cipher
391            .internal_encrypt(plaintext, self.aad)
392            .map_err(CoreError::from)?;
393
394        Ok(Ciphertext::new(&ciphertext))
395    }
396}
397
398// Implement EncryptOperation for GcmEncryptOperation
399impl<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> EncryptOperation<'a, Gcm<B>>
400    for GcmEncryptOperation<'a, B>
401{
402    fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
403        self.nonce = Some(nonce);
404        self
405    }
406
407    fn with_aad(mut self, aad: &'a [u8]) -> Self {
408        self.aad = Some(aad);
409        self
410    }
411
412    fn encrypt(self, plaintext: &'a [u8]) -> std::result::Result<Ciphertext, CoreError> {
413        if self.nonce.is_none() {
414            return Err(CoreError::InvalidParameter {
415                context: "GCM encryption",
416                #[cfg(feature = "std")]
417                message: "Nonce is required for GCM encryption".to_string(),
418            });
419        }
420
421        let ciphertext = self
422            .cipher
423            .internal_encrypt(plaintext, self.aad)
424            .map_err(CoreError::from)?;
425
426        Ok(Ciphertext::new(&ciphertext))
427    }
428}
429
430// Implement Operation for GcmDecryptOperation
431impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Operation<Vec<u8>> for GcmDecryptOperation<'_, B> {
432    fn execute(self) -> std::result::Result<Vec<u8>, CoreError> {
433        Err(CoreError::InvalidParameter {
434            context: "GCM decryption",
435            #[cfg(feature = "std")]
436            message: "Use decrypt method instead".to_string(),
437        })
438    }
439}
440
441// Implement DecryptOperation for GcmDecryptOperation
442impl<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> DecryptOperation<'a, Gcm<B>>
443    for GcmDecryptOperation<'a, B>
444{
445    fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
446        self.nonce = Some(nonce);
447        self
448    }
449
450    fn with_aad(mut self, aad: &'a [u8]) -> Self {
451        self.aad = Some(aad);
452        self
453    }
454
455    fn decrypt(
456        self,
457        ciphertext: &'a <Gcm<B> as SymmetricCipher>::Ciphertext,
458    ) -> std::result::Result<Vec<u8>, CoreError> {
459        if self.nonce.is_none() {
460            return Err(CoreError::InvalidParameter {
461                context: "GCM decryption",
462                #[cfg(feature = "std")]
463                message: "Nonce is required for GCM decryption".to_string(),
464            });
465        }
466
467        self.cipher
468            .internal_decrypt(ciphertext.as_ref(), self.aad)
469            .map_err(CoreError::from)
470    }
471}
472
473#[cfg(test)]
474mod tests;