Skip to main content

noxtls_crypto/sym/encryption/
aes.rs

1// Copyright (c) 2019-2026, Argenox Technologies LLC
2// All rights reserved.
3//
4// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License
5//
6// This file is part of the NoxTLS Library.
7//
8// This program is free software: you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by the
10// Free Software Foundation; version 2 of the License.
11//
12// Alternatively, this file may be used under the terms of a commercial
13// license from Argenox Technologies LLC.
14//
15// See `noxtls/LICENSE` and `noxtls/LICENSE.md` in this repository for full details.
16// CONTACT: info@argenox.com
17
18use crate::internal_alloc::Vec;
19#[cfg(not(feature = "std"))]
20use alloc::boxed::Box;
21use noxtls_core::{Error, Result};
22#[cfg(feature = "std")]
23use std::boxed::Box;
24
25/// Stores expanded AES round keys for block encryption/decryption.
26#[derive(Debug, Clone)]
27pub struct AesCipher {
28    round_keys: Vec<[u8; 16]>,
29    rounds: usize,
30    gcm_table: Box<GhashTable>,
31}
32
33impl AesCipher {
34    /// Builds an AES cipher for 128/192/256-bit keys.
35    ///
36    /// # Arguments
37    /// * `key`: AES key bytes (16, 24, or 32 bytes).
38    ///
39    /// # Returns
40    /// Initialized `AesCipher` with expanded round keys.
41    pub fn noxtls_new(key: &[u8]) -> Result<Self> {
42        let (nk, rounds) = match key.len() {
43            16 => (4, 10),
44            24 => (6, 12),
45            32 => (8, 14),
46            _ => {
47                return Err(Error::InvalidLength(
48                    "aes key length must be 16, 24, or 32 bytes",
49                ))
50            }
51        };
52        let expanded = key_expansion(key, nk, rounds);
53        let mut h = [0_u8; 16];
54        aes_encrypt_block_raw(&expanded, rounds, &mut h);
55        Ok(Self {
56            round_keys: expanded,
57            rounds,
58            gcm_table: Box::new(GhashTable::new(u128::from_be_bytes(h))),
59        })
60    }
61
62    /// Encrypts one 16-byte block in place using AES.
63    ///
64    /// # Arguments
65    /// * `block`: Mutable 16-byte block to encrypt in place.
66    pub fn encrypt_block(&self, block: &mut [u8; 16]) {
67        aes_encrypt_block_raw(&self.round_keys, self.rounds, block);
68    }
69
70    /// Decrypts one 16-byte block in place using AES inverse rounds.
71    ///
72    /// # Arguments
73    /// * `block`: Mutable 16-byte block to decrypt in place.
74    pub fn decrypt_block(&self, block: &mut [u8; 16]) {
75        add_round_key(block, &self.round_keys[self.rounds]);
76        for round in (1..self.rounds).rev() {
77            inv_shift_rows(block);
78            inv_sub_bytes(block);
79            add_round_key(block, &self.round_keys[round]);
80            inv_mix_columns(block);
81        }
82        inv_shift_rows(block);
83        inv_sub_bytes(block);
84        add_round_key(block, &self.round_keys[0]);
85    }
86}
87
88/// Encrypts AES-ECB over full blocks; input length must be multiple of 16.
89///
90/// # Arguments
91/// * `cipher`: Configured AES cipher instance.
92/// * `input`: Block-aligned plaintext bytes.
93///
94/// # Returns
95/// ECB ciphertext bytes with same length as `input`.
96#[cfg(feature = "hazardous-legacy-crypto")]
97pub fn noxtls_aes_ecb_encrypt(cipher: &AesCipher, input: &[u8]) -> Result<Vec<u8>> {
98    if !input.len().is_multiple_of(16) {
99        return Err(Error::InvalidLength("aes ecb input must be block-aligned"));
100    }
101    let mut out = input.to_vec();
102    for chunk in out.chunks_exact_mut(16) {
103        let mut block = [0_u8; 16];
104        block.copy_from_slice(chunk);
105        cipher.encrypt_block(&mut block);
106        chunk.copy_from_slice(&block);
107    }
108    Ok(out)
109}
110
111/// Decrypts AES-ECB over full blocks; input length must be multiple of 16.
112///
113/// # Arguments
114/// * `cipher`: Configured AES cipher instance.
115/// * `input`: Block-aligned ciphertext bytes.
116///
117/// # Returns
118/// ECB plaintext bytes with same length as `input`.
119#[cfg(feature = "hazardous-legacy-crypto")]
120pub fn noxtls_aes_ecb_decrypt(cipher: &AesCipher, input: &[u8]) -> Result<Vec<u8>> {
121    if !input.len().is_multiple_of(16) {
122        return Err(Error::InvalidLength("aes ecb input must be block-aligned"));
123    }
124    let mut out = input.to_vec();
125    for chunk in out.chunks_exact_mut(16) {
126        let mut block = [0_u8; 16];
127        block.copy_from_slice(chunk);
128        cipher.decrypt_block(&mut block);
129        chunk.copy_from_slice(&block);
130    }
131    Ok(out)
132}
133
134/// Encrypts AES-CBC with a 16-byte IV and block-aligned plaintext.
135///
136/// # Arguments
137/// * `cipher`: Configured AES cipher instance.
138/// * `iv`: 16-byte initialization vector.
139/// * `plaintext`: Block-aligned plaintext bytes.
140///
141/// # Returns
142/// CBC ciphertext bytes with same length as `plaintext`.
143pub fn noxtls_aes_cbc_encrypt(
144    cipher: &AesCipher,
145    iv: &[u8; 16],
146    plaintext: &[u8],
147) -> Result<Vec<u8>> {
148    if !plaintext.len().is_multiple_of(16) {
149        return Err(Error::InvalidLength("aes cbc input must be block-aligned"));
150    }
151    let mut out = plaintext.to_vec();
152    let mut prev = *iv;
153    for chunk in out.chunks_exact_mut(16) {
154        for (i, byte) in chunk.iter_mut().enumerate() {
155            *byte ^= prev[i];
156        }
157        let mut block = [0_u8; 16];
158        block.copy_from_slice(chunk);
159        cipher.encrypt_block(&mut block);
160        chunk.copy_from_slice(&block);
161        prev = block;
162    }
163    Ok(out)
164}
165
166/// Decrypts AES-CBC with a 16-byte IV and block-aligned ciphertext.
167///
168/// # Arguments
169/// * `cipher`: Configured AES cipher instance.
170/// * `iv`: 16-byte initialization vector.
171/// * `ciphertext`: Block-aligned ciphertext bytes.
172///
173/// # Returns
174/// CBC plaintext bytes with same length as `ciphertext`.
175pub fn noxtls_aes_cbc_decrypt(
176    cipher: &AesCipher,
177    iv: &[u8; 16],
178    ciphertext: &[u8],
179) -> Result<Vec<u8>> {
180    if !ciphertext.len().is_multiple_of(16) {
181        return Err(Error::InvalidLength("aes cbc input must be block-aligned"));
182    }
183    let mut out = ciphertext.to_vec();
184    let mut prev = *iv;
185    for chunk in out.chunks_exact_mut(16) {
186        let mut cur = [0_u8; 16];
187        cur.copy_from_slice(chunk);
188        let mut block = cur;
189        cipher.decrypt_block(&mut block);
190        for i in 0..16 {
191            block[i] ^= prev[i];
192        }
193        chunk.copy_from_slice(&block);
194        prev = cur;
195    }
196    Ok(out)
197}
198
199/// Applies AES-CTR transformation using a 16-byte initial counter block.
200///
201/// # Arguments
202/// * `cipher`: Configured AES cipher instance.
203/// * `nonce_counter`: Initial 16-byte counter block.
204/// * `input`: Input bytes to transform.
205///
206/// # Returns
207/// Transformed bytes (encryption/decryption are identical in CTR).
208pub fn noxtls_aes_ctr_apply(cipher: &AesCipher, nonce_counter: &[u8; 16], input: &[u8]) -> Vec<u8> {
209    let mut out = vec![0_u8; input.len()];
210    let mut counter = *nonce_counter;
211    let mut offset = 0;
212    while offset < input.len() {
213        let mut stream = counter;
214        cipher.encrypt_block(&mut stream);
215        let chunk_len = (input.len() - offset).min(16);
216        for i in 0..chunk_len {
217            out[offset + i] = input[offset + i] ^ stream[i];
218        }
219        increment_be(&mut counter);
220        offset += chunk_len;
221    }
222    out
223}
224
225/// Applies AES-CFB-128 transformation with a 16-byte IV.
226///
227/// # Arguments
228/// * `cipher`: Configured AES cipher instance.
229/// * `iv`: 16-byte initialization vector/register.
230/// * `input`: Input bytes to transform.
231///
232/// # Returns
233/// Transformed bytes for CFB mode.
234pub fn noxtls_aes_cfb_apply(cipher: &AesCipher, iv: &[u8; 16], input: &[u8]) -> Vec<u8> {
235    noxtls_aes_cfb_encrypt(cipher, iv, input)
236}
237
238/// Encrypts bytes with AES-CFB-128 using a 16-byte IV/register.
239///
240/// # Arguments
241/// * `cipher`: Configured AES cipher instance.
242/// * `iv`: 16-byte initialization vector/register.
243/// * `plaintext`: Plaintext bytes to encrypt.
244///
245/// # Returns
246/// Ciphertext bytes with same length as `plaintext`.
247pub fn noxtls_aes_cfb_encrypt(cipher: &AesCipher, iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
248    aes_cfb_process(cipher, iv, plaintext, true)
249}
250
251/// Decrypts bytes with AES-CFB-128 using a 16-byte IV/register.
252///
253/// # Arguments
254/// * `cipher`: Configured AES cipher instance.
255/// * `iv`: 16-byte initialization vector/register.
256/// * `ciphertext`: Ciphertext bytes to decrypt.
257///
258/// # Returns
259/// Plaintext bytes with same length as `ciphertext`.
260pub fn noxtls_aes_cfb_decrypt(cipher: &AesCipher, iv: &[u8; 16], ciphertext: &[u8]) -> Vec<u8> {
261    aes_cfb_process(cipher, iv, ciphertext, false)
262}
263
264/// Processes AES-CFB-128 encryption/decryption while tracking register updates.
265///
266/// # Arguments
267///
268/// * `cipher` — `&AesCipher`.
269/// * `iv` — `&[u8; 16]`.
270/// * `input` — `&[u8]`.
271/// * `encrypt` — `bool`.
272///
273/// # Returns
274///
275/// `Vec<u8>` produced by `aes_cfb_process` (see implementation).
276///
277/// # Panics
278///
279/// This function does not panic unless otherwise noted.
280fn aes_cfb_process(cipher: &AesCipher, iv: &[u8; 16], input: &[u8], encrypt: bool) -> Vec<u8> {
281    let mut out = vec![0_u8; input.len()];
282    let mut reg = *iv;
283    let mut offset = 0;
284    while offset < input.len() {
285        let mut stream = reg;
286        cipher.encrypt_block(&mut stream);
287        let chunk_len = (input.len() - offset).min(16);
288        for i in 0..chunk_len {
289            out[offset + i] = input[offset + i] ^ stream[i];
290        }
291        if encrypt {
292            shift_register_append(&mut reg, &out[offset..offset + chunk_len]);
293        } else {
294            shift_register_append(&mut reg, &input[offset..offset + chunk_len]);
295        }
296        offset += chunk_len;
297    }
298    out
299}
300
301/// Applies AES-OFB transformation with a 16-byte IV.
302///
303/// # Arguments
304/// * `cipher`: Configured AES cipher instance.
305/// * `iv`: 16-byte initialization vector.
306/// * `input`: Input bytes to transform.
307///
308/// # Returns
309/// Transformed bytes for OFB mode.
310pub fn noxtls_aes_ofb_apply(cipher: &AesCipher, iv: &[u8; 16], input: &[u8]) -> Vec<u8> {
311    let mut out = vec![0_u8; input.len()];
312    let mut stream = *iv;
313    let mut offset = 0;
314    while offset < input.len() {
315        cipher.encrypt_block(&mut stream);
316        let chunk_len = (input.len() - offset).min(16);
317        for i in 0..chunk_len {
318            out[offset + i] = input[offset + i] ^ stream[i];
319        }
320        offset += chunk_len;
321    }
322    out
323}
324
325/// Placeholder API for AES-GCM during ongoing porting work.
326///
327/// # Arguments
328/// * `cipher`: Configured AES cipher instance.
329/// * `nonce`: GCM nonce bytes.
330/// * `aad`: Additional authenticated data bytes.
331/// * `plaintext`: Plaintext bytes to encrypt.
332///
333/// # Returns
334/// `(ciphertext, tag)` pair with 16-byte authentication tag.
335pub fn noxtls_aes_gcm_encrypt(
336    cipher: &AesCipher,
337    nonce: &[u8],
338    aad: &[u8],
339    plaintext: &[u8],
340) -> Result<(Vec<u8>, [u8; 16])> {
341    let j0 = gcm_j0(&cipher.gcm_table, nonce);
342    let mut ctr = j0.to_be_bytes();
343    inc32(&mut ctr);
344    let (ciphertext, s) = gcm_encrypt_and_ghash(cipher, ctr, aad, plaintext);
345    let mut e_j0 = j0.to_be_bytes();
346    cipher.encrypt_block(&mut e_j0);
347    let tag = (u128::from_be_bytes(e_j0) ^ s).to_be_bytes();
348    Ok((ciphertext, tag))
349}
350
351/// Decrypts and authenticates AES-GCM ciphertext/tag with associated data.
352///
353/// # Arguments
354/// * `cipher`: Configured AES cipher instance.
355/// * `nonce`: GCM nonce bytes.
356/// * `aad`: Additional authenticated data bytes.
357/// * `ciphertext`: Ciphertext bytes to decrypt.
358/// * `tag`: 16-byte authentication tag to verify.
359///
360/// # Returns
361/// Decrypted plaintext bytes when tag verification succeeds.
362pub fn noxtls_aes_gcm_decrypt(
363    cipher: &AesCipher,
364    nonce: &[u8],
365    aad: &[u8],
366    ciphertext: &[u8],
367    tag: &[u8; 16],
368) -> Result<Vec<u8>> {
369    let j0 = gcm_j0(&cipher.gcm_table, nonce);
370    let mut ctr = j0.to_be_bytes();
371    inc32(&mut ctr);
372    let s = ghash(&cipher.gcm_table, aad, ciphertext);
373    let mut e_j0 = j0.to_be_bytes();
374    cipher.encrypt_block(&mut e_j0);
375    let expected_tag = (u128::from_be_bytes(e_j0) ^ s).to_be_bytes();
376    if !constant_time_tag_eq(&expected_tag, tag) {
377        return Err(Error::CryptoFailure("aes-gcm authentication failed"));
378    }
379    Ok(gcm_ctr_xor(cipher, ctr, ciphertext))
380}
381
382/// Placeholder API for AES-CCM during ongoing porting work.
383///
384/// # Arguments
385/// * `cipher`: Configured AES cipher instance.
386/// * `nonce`: CCM nonce bytes (7..13 bytes).
387/// * `aad`: Additional authenticated data bytes.
388/// * `plaintext`: Plaintext bytes to encrypt/authenticate.
389///
390/// # Returns
391/// `(ciphertext, tag)` pair with 16-byte authentication tag.
392pub fn noxtls_aes_ccm_encrypt(
393    cipher: &AesCipher,
394    nonce: &[u8],
395    aad: &[u8],
396    plaintext: &[u8],
397) -> Result<(Vec<u8>, [u8; 16])> {
398    noxtls_aes_ccm_encrypt_with_tag_len(cipher, nonce, aad, plaintext, 16)
399}
400
401/// Encrypts and authenticates AES-CCM using a caller-provided tag length.
402///
403/// # Arguments
404/// * `cipher`: Configured AES cipher instance.
405/// * `nonce`: CCM nonce bytes (7..13 bytes).
406/// * `aad`: Additional authenticated data bytes.
407/// * `plaintext`: Plaintext bytes to encrypt/authenticate.
408/// * `tag_len`: Authentication tag length in bytes (even, 4..=16).
409///
410/// # Returns
411/// `(ciphertext, tag)` pair with the tag stored in the leading `tag_len` bytes.
412pub fn noxtls_aes_ccm_encrypt_with_tag_len(
413    cipher: &AesCipher,
414    nonce: &[u8],
415    aad: &[u8],
416    plaintext: &[u8],
417    tag_len: usize,
418) -> Result<(Vec<u8>, [u8; 16])> {
419    if !(7..=13).contains(&nonce.len()) {
420        return Err(Error::InvalidLength("aes-ccm nonce must be 7..13 bytes"));
421    }
422    validate_ccm_tag_len(tag_len)?;
423    let q = 15 - nonce.len();
424    if plaintext.len() >= (1_usize << (8 * q.min(8))) {
425        return Err(Error::InvalidLength(
426            "aes-ccm plaintext too large for nonce",
427        ));
428    }
429    let mut b0 = [0_u8; 16];
430    let aadata_flag = if aad.is_empty() { 0_u8 } else { 0x40 };
431    let m_prime = (((tag_len - 2) / 2) as u8) << 3;
432    let l_prime = (q as u8) - 1;
433    b0[0] = aadata_flag | m_prime | l_prime;
434    b0[1..1 + nonce.len()].copy_from_slice(nonce);
435    encode_len_q(plaintext.len() as u64, q, &mut b0[16 - q..]);
436
437    let mut mac_state = [0_u8; 16];
438    xor_block_in_place(&mut mac_state, &b0);
439    cipher.encrypt_block(&mut mac_state);
440
441    if !aad.is_empty() {
442        let mut aad_blocked = Vec::new();
443        if aad.len() < 0xFF00 {
444            aad_blocked.extend_from_slice(&(aad.len() as u16).to_be_bytes());
445        } else {
446            aad_blocked.extend_from_slice(&[0xFF, 0xFE]);
447            aad_blocked.extend_from_slice(&(aad.len() as u32).to_be_bytes());
448        }
449        aad_blocked.extend_from_slice(aad);
450        pad16(&mut aad_blocked);
451        for chunk in aad_blocked.chunks_exact(16) {
452            let mut blk = [0_u8; 16];
453            blk.copy_from_slice(chunk);
454            xor_block_in_place(&mut mac_state, &blk);
455            cipher.encrypt_block(&mut mac_state);
456        }
457    }
458
459    let mut payload = plaintext.to_vec();
460    pad16(&mut payload);
461    for chunk in payload.chunks_exact(16) {
462        let mut blk = [0_u8; 16];
463        blk.copy_from_slice(chunk);
464        xor_block_in_place(&mut mac_state, &blk);
465        cipher.encrypt_block(&mut mac_state);
466    }
467    let mut tag = mac_state;
468
469    let mut ctr0 = [0_u8; 16];
470    ctr0[0] = l_prime;
471    ctr0[1..1 + nonce.len()].copy_from_slice(nonce);
472    let mut s0 = ctr0;
473    cipher.encrypt_block(&mut s0);
474    for (t, s) in tag.iter_mut().zip(s0) {
475        *t ^= s;
476    }
477    let mut exported_tag = [0_u8; 16];
478    exported_tag[..tag_len].copy_from_slice(&tag[..tag_len]);
479
480    let mut ciphertext = vec![0_u8; plaintext.len()];
481    let mut counter = ctr0;
482    for block_idx in 0..plaintext.len().div_ceil(16) {
483        increment_q_counter(&mut counter, q);
484        let mut stream = counter;
485        cipher.encrypt_block(&mut stream);
486        let start = block_idx * 16;
487        let end = (start + 16).min(plaintext.len());
488        for i in start..end {
489            ciphertext[i] = plaintext[i] ^ stream[i - start];
490        }
491    }
492
493    Ok((ciphertext, exported_tag))
494}
495
496/// Decrypts and authenticates AES-CCM ciphertext/tag with associated data.
497///
498/// # Arguments
499/// * `cipher`: Configured AES cipher instance.
500/// * `nonce`: CCM nonce bytes (7..13 bytes).
501/// * `aad`: Additional authenticated data bytes.
502/// * `ciphertext`: Ciphertext bytes to decrypt/authenticate.
503/// * `tag`: 16-byte authentication tag to verify.
504///
505/// # Returns
506/// Decrypted plaintext bytes when tag verification succeeds.
507pub fn noxtls_aes_ccm_decrypt(
508    cipher: &AesCipher,
509    nonce: &[u8],
510    aad: &[u8],
511    ciphertext: &[u8],
512    tag: &[u8; 16],
513) -> Result<Vec<u8>> {
514    noxtls_aes_ccm_decrypt_with_tag_len(cipher, nonce, aad, ciphertext, tag, 16)
515}
516
517/// Decrypts and authenticates AES-CCM ciphertext/tag with a caller-provided tag length.
518///
519/// # Arguments
520/// * `cipher`: Configured AES cipher instance.
521/// * `nonce`: CCM nonce bytes (7..13 bytes).
522/// * `aad`: Additional authenticated data bytes.
523/// * `ciphertext`: Ciphertext bytes to decrypt/authenticate.
524/// * `tag`: Authentication tag bytes stored in the leading `tag_len` bytes.
525/// * `tag_len`: Authentication tag length in bytes (even, 4..=16).
526///
527/// # Returns
528/// Decrypted plaintext bytes when tag verification succeeds.
529pub fn noxtls_aes_ccm_decrypt_with_tag_len(
530    cipher: &AesCipher,
531    nonce: &[u8],
532    aad: &[u8],
533    ciphertext: &[u8],
534    tag: &[u8; 16],
535    tag_len: usize,
536) -> Result<Vec<u8>> {
537    if !(7..=13).contains(&nonce.len()) {
538        return Err(Error::InvalidLength("aes-ccm nonce must be 7..13 bytes"));
539    }
540    validate_ccm_tag_len(tag_len)?;
541    let q = 15 - nonce.len();
542    if ciphertext.len() >= (1_usize << (8 * q.min(8))) {
543        return Err(Error::InvalidLength(
544            "aes-ccm ciphertext too large for nonce",
545        ));
546    }
547    let l_prime = (q as u8) - 1;
548
549    let mut ctr0 = [0_u8; 16];
550    ctr0[0] = l_prime;
551    ctr0[1..1 + nonce.len()].copy_from_slice(nonce);
552
553    let mut plaintext = vec![0_u8; ciphertext.len()];
554    let mut counter = ctr0;
555    for block_idx in 0..ciphertext.len().div_ceil(16) {
556        increment_q_counter(&mut counter, q);
557        let mut stream = counter;
558        cipher.encrypt_block(&mut stream);
559        let start = block_idx * 16;
560        let end = (start + 16).min(ciphertext.len());
561        for i in start..end {
562            plaintext[i] = ciphertext[i] ^ stream[i - start];
563        }
564    }
565
566    let mut b0 = [0_u8; 16];
567    let aadata_flag = if aad.is_empty() { 0_u8 } else { 0x40 };
568    let m_prime = (((tag_len - 2) / 2) as u8) << 3;
569    b0[0] = aadata_flag | m_prime | l_prime;
570    b0[1..1 + nonce.len()].copy_from_slice(nonce);
571    encode_len_q(plaintext.len() as u64, q, &mut b0[16 - q..]);
572
573    let mut mac_state = [0_u8; 16];
574    xor_block_in_place(&mut mac_state, &b0);
575    cipher.encrypt_block(&mut mac_state);
576
577    if !aad.is_empty() {
578        let mut aad_blocked = Vec::new();
579        if aad.len() < 0xFF00 {
580            aad_blocked.extend_from_slice(&(aad.len() as u16).to_be_bytes());
581        } else {
582            aad_blocked.extend_from_slice(&[0xFF, 0xFE]);
583            aad_blocked.extend_from_slice(&(aad.len() as u32).to_be_bytes());
584        }
585        aad_blocked.extend_from_slice(aad);
586        pad16(&mut aad_blocked);
587        for chunk in aad_blocked.chunks_exact(16) {
588            let mut blk = [0_u8; 16];
589            blk.copy_from_slice(chunk);
590            xor_block_in_place(&mut mac_state, &blk);
591            cipher.encrypt_block(&mut mac_state);
592        }
593    }
594
595    let mut payload = plaintext.clone();
596    pad16(&mut payload);
597    for chunk in payload.chunks_exact(16) {
598        let mut blk = [0_u8; 16];
599        blk.copy_from_slice(chunk);
600        xor_block_in_place(&mut mac_state, &blk);
601        cipher.encrypt_block(&mut mac_state);
602    }
603    let mut expected_tag = mac_state;
604    let mut s0 = ctr0;
605    cipher.encrypt_block(&mut s0);
606    for (t, s) in expected_tag.iter_mut().zip(s0) {
607        *t ^= s;
608    }
609    if !constant_time_tag_eq_prefix(&expected_tag, tag, tag_len) {
610        return Err(Error::CryptoFailure("aes-ccm authentication failed"));
611    }
612    Ok(plaintext)
613}
614
615/// Compares fixed-size authentication tags in constant time.
616///
617/// # Arguments
618///
619/// * `expected` — `&[u8; 16]`.
620/// * `received` — `&[u8; 16]`.
621///
622/// # Returns
623///
624/// `bool` produced by `constant_time_tag_eq` (see implementation).
625///
626/// # Panics
627///
628/// This function does not panic unless otherwise noted.
629fn constant_time_tag_eq(expected: &[u8; 16], received: &[u8; 16]) -> bool {
630    let mut diff = 0_u8;
631    for (&left, &right) in expected.iter().zip(received.iter()) {
632        diff |= left ^ right;
633    }
634    diff == 0
635}
636
637fn constant_time_tag_eq_prefix(expected: &[u8; 16], received: &[u8; 16], tag_len: usize) -> bool {
638    let mut diff = 0_u8;
639    for (&left, &right) in expected[..tag_len].iter().zip(received[..tag_len].iter()) {
640        diff |= left ^ right;
641    }
642    diff == 0
643}
644
645/// Placeholder API for AES-XTS during ongoing porting work.
646///
647/// # Arguments
648/// * `cipher_a`: Data-key AES instance.
649/// * `cipher_b`: Tweak-key AES instance.
650/// * `tweak`: Initial 16-byte tweak value.
651/// * `plaintext`: Block-aligned plaintext bytes.
652///
653/// # Returns
654/// XTS ciphertext bytes with same length as `plaintext`.
655pub fn noxtls_aes_xts_encrypt(
656    cipher_a: &AesCipher,
657    cipher_b: &AesCipher,
658    tweak: &[u8; 16],
659    plaintext: &[u8],
660) -> Result<Vec<u8>> {
661    aes_xts_crypt(cipher_a, cipher_b, tweak, plaintext, true)
662}
663
664/// Decrypts AES-XTS over a data unit, including ciphertext-stealing for partial trailing block.
665///
666/// # Arguments
667/// * `cipher_a`: Data-key AES instance.
668/// * `cipher_b`: Tweak-key AES instance.
669/// * `tweak`: Initial 16-byte tweak value.
670/// * `ciphertext`: Ciphertext bytes to decrypt.
671///
672/// # Returns
673/// XTS plaintext bytes with same length as `ciphertext`.
674pub fn noxtls_aes_xts_decrypt(
675    cipher_a: &AesCipher,
676    cipher_b: &AesCipher,
677    tweak: &[u8; 16],
678    ciphertext: &[u8],
679) -> Result<Vec<u8>> {
680    aes_xts_crypt(cipher_a, cipher_b, tweak, ciphertext, false)
681}
682
683/// Applies AES-XTS encryption or decryption with ciphertext stealing for non-block-aligned inputs.
684///
685/// # Arguments
686///
687/// * `cipher_a` — Data-path AES instance.
688/// * `cipher_b` — Tweak-path AES instance used to derive the initial tweak.
689/// * `tweak` — 16-byte starting tweak block.
690/// * `input` — Plaintext (encrypt) or ciphertext (decrypt), at least 16 bytes.
691/// * `encrypt` — `true` to encrypt, `false` to decrypt.
692///
693/// # Returns
694///
695/// On success, output bytes matching `input` length.
696///
697/// # Errors
698///
699/// Returns `noxtls_core::Error` when `input` is shorter than one block or ciphertext stealing paths fail internal checks.
700///
701/// # Panics
702///
703/// This function does not panic.
704fn aes_xts_crypt(
705    cipher_a: &AesCipher,
706    cipher_b: &AesCipher,
707    tweak: &[u8; 16],
708    input: &[u8],
709    encrypt: bool,
710) -> Result<Vec<u8>> {
711    if input.len() < 16 {
712        return Err(Error::InvalidLength(
713            "aes-xts input must be at least one full 16-byte block",
714        ));
715    }
716    let mut out = vec![0_u8; input.len()];
717    let full_blocks = input.len() / 16;
718    let rem = input.len() % 16;
719
720    let mut tw = *tweak;
721    cipher_b.encrypt_block(&mut tw);
722
723    if rem == 0 {
724        for block_idx in 0..full_blocks {
725            let start = block_idx * 16;
726            let mut block = [0_u8; 16];
727            block.copy_from_slice(&input[start..start + 16]);
728            xor_block_in_place(&mut block, &tw);
729            if encrypt {
730                cipher_a.encrypt_block(&mut block);
731            } else {
732                cipher_a.decrypt_block(&mut block);
733            }
734            xor_block_in_place(&mut block, &tw);
735            out[start..start + 16].copy_from_slice(&block);
736            xts_mul_x(&mut tw);
737        }
738        return Ok(out);
739    }
740
741    // Process all but the final full block that participates in ciphertext stealing.
742    for block_idx in 0..(full_blocks - 1) {
743        let start = block_idx * 16;
744        let mut block = [0_u8; 16];
745        block.copy_from_slice(&input[start..start + 16]);
746        xor_block_in_place(&mut block, &tw);
747        if encrypt {
748            cipher_a.encrypt_block(&mut block);
749        } else {
750            cipher_a.decrypt_block(&mut block);
751        }
752        xor_block_in_place(&mut block, &tw);
753        out[start..start + 16].copy_from_slice(&block);
754        xts_mul_x(&mut tw);
755    }
756
757    let mut tw_next = tw;
758    xts_mul_x(&mut tw_next);
759    let last_full_start = (full_blocks - 1) * 16;
760    let partial_start = full_blocks * 16;
761
762    if encrypt {
763        let mut block = [0_u8; 16];
764        block.copy_from_slice(&input[last_full_start..last_full_start + 16]);
765        xor_block_in_place(&mut block, &tw);
766        cipher_a.encrypt_block(&mut block);
767        xor_block_in_place(&mut block, &tw);
768
769        // C_m is first r bytes of C*.
770        out[partial_start..].copy_from_slice(&block[..rem]);
771
772        // P* = P_m || C*[r..16], then encrypted with next tweak for C_{m-1}.
773        let mut p_star = [0_u8; 16];
774        p_star[..rem].copy_from_slice(&input[partial_start..]);
775        p_star[rem..].copy_from_slice(&block[rem..]);
776        xor_block_in_place(&mut p_star, &tw_next);
777        cipher_a.encrypt_block(&mut p_star);
778        xor_block_in_place(&mut p_star, &tw_next);
779        out[last_full_start..last_full_start + 16].copy_from_slice(&p_star);
780    } else {
781        let mut c_m_minus_1 = [0_u8; 16];
782        c_m_minus_1.copy_from_slice(&input[last_full_start..last_full_start + 16]);
783        xor_block_in_place(&mut c_m_minus_1, &tw_next);
784        cipher_a.decrypt_block(&mut c_m_minus_1);
785        xor_block_in_place(&mut c_m_minus_1, &tw_next);
786
787        // P_m is the first r bytes of decrypted C_{m-1}.
788        out[partial_start..].copy_from_slice(&c_m_minus_1[..rem]);
789
790        // Reconstruct C* = C_m || tail(P*), then decrypt with current tweak for P_{m-1}.
791        let mut c_star = [0_u8; 16];
792        c_star[..rem].copy_from_slice(&input[partial_start..]);
793        c_star[rem..].copy_from_slice(&c_m_minus_1[rem..]);
794        xor_block_in_place(&mut c_star, &tw);
795        cipher_a.decrypt_block(&mut c_star);
796        xor_block_in_place(&mut c_star, &tw);
797        out[last_full_start..last_full_start + 16].copy_from_slice(&c_star);
798    }
799
800    Ok(out)
801}
802
803/// Expands AES key material into round keys for encryption and decryption.
804///
805/// # Arguments
806///
807/// * `key` — `&[u8]`.
808/// * `nk` — `usize`.
809/// * `rounds` — `usize`.
810///
811/// # Returns
812///
813/// `Vec<[u8` produced by `key_expansion` (see implementation).
814///
815/// # Panics
816///
817/// This function does not panic unless otherwise noted.
818fn key_expansion(key: &[u8], nk: usize, rounds: usize) -> Vec<[u8; 16]> {
819    let total_words = 4 * (rounds + 1);
820    let mut w = vec![0_u32; total_words];
821    for (i, word) in w.iter_mut().enumerate().take(nk) {
822        let idx = i * 4;
823        *word = u32::from_be_bytes([key[idx], key[idx + 1], key[idx + 2], key[idx + 3]]);
824    }
825    for i in nk..total_words {
826        let mut temp = w[i - 1];
827        if i % nk == 0 {
828            temp = sub_word(rot_word(temp)) ^ (u32::from(RCON[i / nk - 1]) << 24);
829        } else if nk > 6 && i % nk == 4 {
830            temp = sub_word(temp);
831        }
832        w[i] = w[i - nk] ^ temp;
833    }
834    let mut keys = Vec::with_capacity(rounds + 1);
835    for r in 0..=rounds {
836        let mut key_block = [0_u8; 16];
837        for c in 0..4 {
838            key_block[c * 4..(c + 1) * 4].copy_from_slice(&w[r * 4 + c].to_be_bytes());
839        }
840        keys.push(key_block);
841    }
842    keys
843}
844
845/// Rotates one 32-bit word by one byte to the left.
846///
847/// # Arguments
848///
849/// * `word` — `u32`.
850///
851/// # Returns
852///
853/// `u32` produced by `rot_word` (see implementation).
854///
855/// # Panics
856///
857/// This function does not panic unless otherwise noted.
858fn rot_word(word: u32) -> u32 {
859    word.rotate_left(8)
860}
861
862/// Applies AES S-box substitution to each byte of a 32-bit word.
863///
864/// # Arguments
865///
866/// * `word` — `u32`.
867///
868/// # Returns
869///
870/// `u32` produced by `sub_word` (see implementation).
871///
872/// # Panics
873///
874/// This function does not panic unless otherwise noted.
875fn sub_word(word: u32) -> u32 {
876    let bytes = word.to_be_bytes();
877    u32::from_be_bytes([
878        SBOX[usize::from(bytes[0])],
879        SBOX[usize::from(bytes[1])],
880        SBOX[usize::from(bytes[2])],
881        SBOX[usize::from(bytes[3])],
882    ])
883}
884
885/// XORs one round key into current state.
886///
887/// # Arguments
888///
889/// * `state` — `&mut [u8; 16]`.
890/// * `round_key` — `&[u8; 16]`.
891///
892/// # Returns
893///
894/// `()` when there is no return data.
895///
896/// # Panics
897///
898/// This function does not panic unless otherwise noted.
899fn add_round_key(state: &mut [u8; 16], round_key: &[u8; 16]) {
900    for i in 0..16 {
901        state[i] ^= round_key[i];
902    }
903}
904
905/// Applies forward AES S-box to every state byte.
906///
907/// # Arguments
908///
909/// * `state` — `&mut [u8; 16]`.
910///
911/// # Returns
912///
913/// `()` when there is no return data.
914///
915/// # Panics
916///
917/// This function does not panic unless otherwise noted.
918fn sub_bytes(state: &mut [u8; 16]) {
919    for byte in state {
920        *byte = SBOX[usize::from(*byte)];
921    }
922}
923
924/// Applies inverse AES S-box to every state byte.
925///
926/// # Arguments
927///
928/// * `state` — `&mut [u8; 16]`.
929///
930/// # Returns
931///
932/// `()` when there is no return data.
933///
934/// # Panics
935///
936/// This function does not panic unless otherwise noted.
937fn inv_sub_bytes(state: &mut [u8; 16]) {
938    for byte in state {
939        *byte = INV_SBOX[usize::from(*byte)];
940    }
941}
942
943/// Performs AES row shifts in forward direction.
944///
945/// # Arguments
946///
947/// * `state` — `&mut [u8; 16]`.
948///
949/// # Returns
950///
951/// `()` when there is no return data.
952///
953/// # Panics
954///
955/// This function does not panic unless otherwise noted.
956fn shift_rows(state: &mut [u8; 16]) {
957    let mut tmp = *state;
958    tmp[1] = state[5];
959    tmp[5] = state[9];
960    tmp[9] = state[13];
961    tmp[13] = state[1];
962    tmp[2] = state[10];
963    tmp[6] = state[14];
964    tmp[10] = state[2];
965    tmp[14] = state[6];
966    tmp[3] = state[15];
967    tmp[7] = state[3];
968    tmp[11] = state[7];
969    tmp[15] = state[11];
970    *state = tmp;
971}
972
973/// Performs AES row shifts in inverse direction.
974///
975/// # Arguments
976///
977/// * `state` — `&mut [u8; 16]`.
978///
979/// # Returns
980///
981/// `()` when there is no return data.
982///
983/// # Panics
984///
985/// This function does not panic unless otherwise noted.
986fn inv_shift_rows(state: &mut [u8; 16]) {
987    let mut tmp = *state;
988    tmp[1] = state[13];
989    tmp[5] = state[1];
990    tmp[9] = state[5];
991    tmp[13] = state[9];
992    tmp[2] = state[10];
993    tmp[6] = state[14];
994    tmp[10] = state[2];
995    tmp[14] = state[6];
996    tmp[3] = state[7];
997    tmp[7] = state[11];
998    tmp[11] = state[15];
999    tmp[15] = state[3];
1000    *state = tmp;
1001}
1002
1003/// Mixes each AES state column using Rijndael field multiplication.
1004///
1005/// # Arguments
1006///
1007/// * `state` — `&mut [u8; 16]`.
1008///
1009/// # Returns
1010///
1011/// `()` when there is no return data.
1012///
1013/// # Panics
1014///
1015/// This function does not panic unless otherwise noted.
1016fn mix_columns(state: &mut [u8; 16]) {
1017    for c in 0..4 {
1018        let i = c * 4;
1019        let a0 = state[i];
1020        let a1 = state[i + 1];
1021        let a2 = state[i + 2];
1022        let a3 = state[i + 3];
1023        let mix = a0 ^ a1 ^ a2 ^ a3;
1024        state[i] = a0 ^ mix ^ xtime(a0 ^ a1);
1025        state[i + 1] = a1 ^ mix ^ xtime(a1 ^ a2);
1026        state[i + 2] = a2 ^ mix ^ xtime(a2 ^ a3);
1027        state[i + 3] = a3 ^ mix ^ xtime(a3 ^ a0);
1028    }
1029}
1030
1031/// Inversely mixes each AES state column for decryption rounds.
1032///
1033/// # Arguments
1034///
1035/// * `state` — `&mut [u8; 16]`.
1036///
1037/// # Returns
1038///
1039/// `()` when there is no return data.
1040///
1041/// # Panics
1042///
1043/// This function does not panic unless otherwise noted.
1044fn inv_mix_columns(state: &mut [u8; 16]) {
1045    for c in 0..4 {
1046        let i = c * 4;
1047        let a0 = state[i];
1048        let a1 = state[i + 1];
1049        let a2 = state[i + 2];
1050        let a3 = state[i + 3];
1051        let u = xtime(xtime(a0 ^ a2));
1052        let v = xtime(xtime(a1 ^ a3));
1053        state[i] = a0 ^ u;
1054        state[i + 1] = a1 ^ v;
1055        state[i + 2] = a2 ^ u;
1056        state[i + 3] = a3 ^ v;
1057    }
1058    mix_columns(state);
1059}
1060
1061/// Multiplies two bytes in GF(2^8) with AES reduction polynomial.
1062///
1063/// # Arguments
1064///
1065/// * `a` — `u8`.
1066/// * `b` — `u8`.
1067///
1068/// # Returns
1069///
1070/// `u8` produced by `gf_mul` (see implementation).
1071///
1072/// # Panics
1073///
1074/// This function does not panic unless otherwise noted.
1075#[inline(always)]
1076fn xtime(value: u8) -> u8 {
1077    let shifted = value << 1;
1078    if (value & 0x80) != 0 {
1079        shifted ^ 0x1b
1080    } else {
1081        shifted
1082    }
1083}
1084
1085/// Increments a 16-byte big-endian counter block in place.
1086///
1087/// # Arguments
1088///
1089/// * `counter` — `&mut [u8; 16]`.
1090///
1091/// # Returns
1092///
1093/// `()` when there is no return data.
1094///
1095/// # Panics
1096///
1097/// This function does not panic unless otherwise noted.
1098fn increment_be(counter: &mut [u8; 16]) {
1099    for byte in counter.iter_mut().rev() {
1100        *byte = byte.wrapping_add(1);
1101        if *byte != 0 {
1102            break;
1103        }
1104    }
1105}
1106
1107/// Computes GHASH over AAD and ciphertext for GCM authentication.
1108///
1109/// # Arguments
1110///
1111/// * `h` — `u128`.
1112/// * `aad` — `&[u8]`.
1113/// * `ciphertext` — `&[u8]`.
1114///
1115/// # Returns
1116///
1117/// `u128` produced by `ghash` (see implementation).
1118///
1119/// # Panics
1120///
1121/// This function does not panic unless otherwise noted.
1122fn ghash(table: &GhashTable, aad: &[u8], ciphertext: &[u8]) -> u128 {
1123    let mut y = 0_u128;
1124    ghash_padded_update(&mut y, table, aad);
1125    ghash_padded_update(&mut y, table, ciphertext);
1126    let lengths = (((aad.len() as u128) * 8) << 64) | ((ciphertext.len() as u128) * 8);
1127    table.mul(y ^ lengths)
1128}
1129
1130fn ghash_padded_update(y: &mut u128, table: &GhashTable, data: &[u8]) {
1131    let mut chunks = data.chunks_exact(16);
1132    for chunk in &mut chunks {
1133        let x = u128::from_be_bytes(chunk.try_into().expect("16-byte chunk"));
1134        *y = table.mul(*y ^ x);
1135    }
1136    let rem = chunks.remainder();
1137    if !rem.is_empty() {
1138        let mut block = [0_u8; 16];
1139        block[..rem.len()].copy_from_slice(rem);
1140        *y = table.mul(*y ^ u128::from_be_bytes(block));
1141    }
1142}
1143
1144fn aes_encrypt_block_raw(round_keys: &[[u8; 16]], rounds: usize, block: &mut [u8; 16]) {
1145    add_round_key(block, &round_keys[0]);
1146    for round_key in round_keys.iter().take(rounds).skip(1) {
1147        sub_bytes(block);
1148        shift_rows(block);
1149        mix_columns(block);
1150        add_round_key(block, round_key);
1151    }
1152    sub_bytes(block);
1153    shift_rows(block);
1154    add_round_key(block, &round_keys[rounds]);
1155}
1156
1157/// Multiplies two elements in GF(2^128) with GCM reduction polynomial.
1158///
1159/// # Arguments
1160///
1161/// * `x` — `u128`.
1162/// * `y` — `u128`.
1163///
1164/// # Returns
1165///
1166/// `u128` produced by `gf128_mul` (see implementation).
1167///
1168/// # Panics
1169///
1170/// This function does not panic unless otherwise noted.
1171#[cfg(test)]
1172fn gf128_mul_slow(mut x: u128, mut y: u128) -> u128 {
1173    let mut z = 0_u128;
1174    for _ in 0..128 {
1175        if (x & (1_u128 << 127)) != 0 {
1176            z ^= y;
1177        }
1178        let lsb = y & 1;
1179        y >>= 1;
1180        if lsb != 0 {
1181            y ^= 0xe1_u128 << 120;
1182        }
1183        x <<= 1;
1184    }
1185    z
1186}
1187
1188#[derive(Debug, Clone)]
1189struct GhashTable {
1190    nibbles: [[u128; 16]; 32],
1191}
1192
1193impl GhashTable {
1194    fn new(h: u128) -> Self {
1195        let mut nibbles = [[0_u128; 16]; 32];
1196        let mut basis = [
1197            h,
1198            gcm_mul_x(h),
1199            gcm_mul_x(gcm_mul_x(h)),
1200            gcm_mul_x(gcm_mul_x(gcm_mul_x(h))),
1201        ];
1202
1203        for row in &mut nibbles {
1204            for (nibble, slot) in row.iter_mut().enumerate() {
1205                let mut value = 0_u128;
1206                if (nibble & 0x8) != 0 {
1207                    value ^= basis[0];
1208                }
1209                if (nibble & 0x4) != 0 {
1210                    value ^= basis[1];
1211                }
1212                if (nibble & 0x2) != 0 {
1213                    value ^= basis[2];
1214                }
1215                if (nibble & 0x1) != 0 {
1216                    value ^= basis[3];
1217                }
1218                *slot = value;
1219            }
1220            for word in &mut basis {
1221                *word = gcm_mul_x4(*word);
1222            }
1223        }
1224
1225        Self { nibbles }
1226    }
1227
1228    #[inline(always)]
1229    fn mul(&self, x: u128) -> u128 {
1230        let bytes = x.to_be_bytes();
1231        let mut z = 0_u128;
1232        for (byte_idx, byte) in bytes.iter().copied().enumerate() {
1233            z ^= self.nibbles[byte_idx * 2][(byte >> 4) as usize];
1234            z ^= self.nibbles[byte_idx * 2 + 1][(byte & 0x0f) as usize];
1235        }
1236        z
1237    }
1238}
1239
1240#[inline(always)]
1241fn gcm_mul_x(mut value: u128) -> u128 {
1242    let lsb = value & 1;
1243    value >>= 1;
1244    if lsb != 0 {
1245        value ^= 0xe1_u128 << 120;
1246    }
1247    value
1248}
1249
1250#[inline(always)]
1251fn gcm_mul_x4(mut value: u128) -> u128 {
1252    value = gcm_mul_x(value);
1253    value = gcm_mul_x(value);
1254    value = gcm_mul_x(value);
1255    gcm_mul_x(value)
1256}
1257
1258/// Builds J0 nonce block per GCM specification.
1259///
1260/// # Arguments
1261///
1262/// * `h` — `u128`.
1263/// * `nonce` — `&[u8]`.
1264///
1265/// # Returns
1266///
1267/// `u128` produced by `gcm_j0` (see implementation).
1268///
1269/// # Panics
1270///
1271/// This function does not panic unless otherwise noted.
1272fn gcm_j0(table: &GhashTable, nonce: &[u8]) -> u128 {
1273    if nonce.len() == 12 {
1274        let mut j = [0_u8; 16];
1275        j[..12].copy_from_slice(nonce);
1276        j[15] = 1;
1277        return u128::from_be_bytes(j);
1278    }
1279    let mut y = 0_u128;
1280    ghash_padded_update(&mut y, table, nonce);
1281    let len_block = (nonce.len() as u128) * 8;
1282    table.mul(y ^ len_block)
1283}
1284
1285fn gcm_encrypt_and_ghash(
1286    cipher: &AesCipher,
1287    mut counter: [u8; 16],
1288    aad: &[u8],
1289    plaintext: &[u8],
1290) -> (Vec<u8>, u128) {
1291    let mut y = 0_u128;
1292    ghash_padded_update(&mut y, &cipher.gcm_table, aad);
1293
1294    let mut out = vec![0_u8; plaintext.len()];
1295    let mut chunks = plaintext.chunks_exact(16);
1296    for (block_idx, chunk) in chunks.by_ref().enumerate() {
1297        let mut stream = counter;
1298        cipher.encrypt_block(&mut stream);
1299        let offset = block_idx * 16;
1300        let mut block = [0_u8; 16];
1301        for i in 0..16 {
1302            let byte = chunk[i] ^ stream[i];
1303            out[offset + i] = byte;
1304            block[i] = byte;
1305        }
1306        y = cipher.gcm_table.mul(y ^ u128::from_be_bytes(block));
1307        inc32(&mut counter);
1308    }
1309
1310    let rem = chunks.remainder();
1311    if !rem.is_empty() {
1312        let offset = plaintext.len() - rem.len();
1313        let mut stream = counter;
1314        cipher.encrypt_block(&mut stream);
1315        let mut block = [0_u8; 16];
1316        for i in 0..rem.len() {
1317            let byte = rem[i] ^ stream[i];
1318            out[offset + i] = byte;
1319            block[i] = byte;
1320        }
1321        y = cipher.gcm_table.mul(y ^ u128::from_be_bytes(block));
1322    }
1323
1324    let lengths = (((aad.len() as u128) * 8) << 64) | ((plaintext.len() as u128) * 8);
1325    (out, cipher.gcm_table.mul(y ^ lengths))
1326}
1327
1328/// Applies GCM counter-mode keystream XOR starting from provided counter block.
1329///
1330/// # Arguments
1331///
1332/// * `cipher` — `&AesCipher`.
1333/// * `initial_ctr` — `u128`.
1334/// * `input` — `&[u8]`.
1335///
1336/// # Returns
1337///
1338/// `Vec<u8>` produced by `gcm_ctr_xor` (see implementation).
1339///
1340/// # Panics
1341///
1342/// This function does not panic unless otherwise noted.
1343fn gcm_ctr_xor(cipher: &AesCipher, mut counter: [u8; 16], input: &[u8]) -> Vec<u8> {
1344    let mut out = vec![0_u8; input.len()];
1345    let mut chunks = input.chunks_exact(16);
1346    for (block_idx, chunk) in chunks.by_ref().enumerate() {
1347        let mut stream = counter;
1348        cipher.encrypt_block(&mut stream);
1349        let offset = block_idx * 16;
1350        for i in 0..16 {
1351            out[offset + i] = chunk[i] ^ stream[i];
1352        }
1353        inc32(&mut counter);
1354    }
1355
1356    let rem = chunks.remainder();
1357    if !rem.is_empty() {
1358        let offset = input.len() - rem.len();
1359        let mut stream = counter;
1360        cipher.encrypt_block(&mut stream);
1361        for i in 0..rem.len() {
1362            out[offset + i] = rem[i] ^ stream[i];
1363        }
1364    }
1365    out
1366}
1367
1368/// Increments low 32-bit GCM counter portion of 128-bit counter block.
1369///
1370/// # Arguments
1371///
1372/// * `counter` — `&mut u128`.
1373///
1374/// # Returns
1375///
1376/// `()` when there is no return data.
1377///
1378/// # Panics
1379///
1380/// This function does not panic unless otherwise noted.
1381/// Increments low 32-bit GCM counter portion in-place.
1382///
1383/// # Arguments
1384///
1385/// * `counter` — `&mut [u8; 16]`.
1386///
1387/// # Returns
1388///
1389/// `()` when there is no return data.
1390///
1391/// # Panics
1392///
1393/// This function does not panic unless otherwise noted.
1394fn inc32(counter: &mut [u8; 16]) {
1395    for i in (12..16).rev() {
1396        counter[i] = counter[i].wrapping_add(1);
1397        if counter[i] != 0 {
1398            break;
1399        }
1400    }
1401}
1402
1403/// Pads byte vector with zeroes to reach next multiple of 16.
1404///
1405/// # Arguments
1406///
1407/// * `data` — `&mut Vec<u8>`.
1408///
1409/// # Returns
1410///
1411/// `()` when there is no return data.
1412///
1413/// # Panics
1414///
1415/// This function does not panic unless otherwise noted.
1416fn pad16(data: &mut Vec<u8>) {
1417    let rem = data.len() % 16;
1418    if rem != 0 {
1419        data.resize(data.len() + (16 - rem), 0);
1420    }
1421}
1422
1423fn validate_ccm_tag_len(tag_len: usize) -> Result<()> {
1424    if (4..=16).contains(&tag_len) && tag_len.is_multiple_of(2) {
1425        Ok(())
1426    } else {
1427        Err(Error::InvalidLength(
1428            "aes-ccm tag length must be an even value in 4..=16",
1429        ))
1430    }
1431}
1432
1433/// Encodes length field in CCM q-byte big-endian form.
1434///
1435/// # Arguments
1436///
1437/// * `len` — `u64`.
1438/// * `q` — `usize`.
1439/// * `out` — `&mut [u8]`.
1440///
1441/// # Returns
1442///
1443/// `()` when there is no return data.
1444///
1445/// # Panics
1446///
1447/// This function does not panic unless otherwise noted.
1448fn encode_len_q(len: u64, q: usize, out: &mut [u8]) {
1449    for i in 0..q {
1450        out[q - 1 - i] = ((len >> (8 * i)) & 0xFF) as u8;
1451    }
1452}
1453
1454/// Increments CCM q-byte counter region in-place.
1455///
1456/// # Arguments
1457///
1458/// * `counter` — `&mut [u8; 16]`.
1459/// * `q` — `usize`.
1460///
1461/// # Returns
1462///
1463/// `()` when there is no return data.
1464///
1465/// # Panics
1466///
1467/// This function does not panic unless otherwise noted.
1468fn increment_q_counter(counter: &mut [u8; 16], q: usize) {
1469    for i in (16 - q..16).rev() {
1470        counter[i] = counter[i].wrapping_add(1);
1471        if counter[i] != 0 {
1472            break;
1473        }
1474    }
1475}
1476
1477/// XORs one 16-byte block into another in-place.
1478///
1479/// # Arguments
1480///
1481/// * `dst` — `&mut [u8; 16]`.
1482/// * `src` — `&[u8; 16]`.
1483///
1484/// # Returns
1485///
1486/// `()` when there is no return data.
1487///
1488/// # Panics
1489///
1490/// This function does not panic unless otherwise noted.
1491fn xor_block_in_place(dst: &mut [u8; 16], src: &[u8; 16]) {
1492    for i in 0..16 {
1493        dst[i] ^= src[i];
1494    }
1495}
1496
1497/// Shifts CFB register left by segment length and appends segment bytes.
1498///
1499/// # Arguments
1500///
1501/// * `reg` — `&mut [u8; 16]`.
1502/// * `segment` — `&[u8]`.
1503///
1504/// # Returns
1505///
1506/// `()` when there is no return data.
1507///
1508/// # Panics
1509///
1510/// This function does not panic unless otherwise noted.
1511fn shift_register_append(reg: &mut [u8; 16], segment: &[u8]) {
1512    debug_assert!(segment.len() <= 16);
1513    if segment.len() == 16 {
1514        reg.copy_from_slice(segment);
1515        return;
1516    }
1517    let keep = 16 - segment.len();
1518    reg.copy_within(segment.len().., 0);
1519    reg[keep..].copy_from_slice(segment);
1520}
1521
1522/// Multiplies XTS tweak by x over GF(2^128) with polynomial x^128 + x^7 + x^2 + x + 1.
1523///
1524/// # Arguments
1525///
1526/// * `tweak` — `&mut [u8; 16]`.
1527///
1528/// # Returns
1529///
1530/// `()` when there is no return data.
1531///
1532/// # Panics
1533///
1534/// This function does not panic unless otherwise noted.
1535fn xts_mul_x(tweak: &mut [u8; 16]) {
1536    let mut carry = 0_u8;
1537    for byte in tweak.iter_mut() {
1538        let next_carry = (*byte & 0x80) >> 7;
1539        *byte = (*byte << 1) | carry;
1540        carry = next_carry;
1541    }
1542    if carry != 0 {
1543        tweak[0] ^= 0x87;
1544    }
1545}
1546
1547const RCON: [u8; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
1548
1549const SBOX: [u8; 256] = [
1550    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
1551    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
1552    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
1553    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
1554    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
1555    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
1556    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
1557    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
1558    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
1559    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
1560    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
1561    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
1562    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
1563    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
1564    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
1565    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
1566];
1567
1568const INV_SBOX: [u8; 256] = [
1569    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
1570    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
1571    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
1572    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
1573    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
1574    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
1575    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
1576    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
1577    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
1578    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
1579    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
1580    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
1581    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
1582    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
1583    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
1584    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
1585];
1586
1587#[cfg(test)]
1588mod tests {
1589    use super::{
1590        gf128_mul_slow, noxtls_aes_gcm_decrypt, noxtls_aes_gcm_encrypt, AesCipher, GhashTable,
1591    };
1592    use crate::internal_alloc::Vec;
1593
1594    fn decode_hex(hex: &str) -> Vec<u8> {
1595        let mut out = Vec::with_capacity(hex.len() / 2);
1596        let bytes = hex.as_bytes();
1597        for pair in bytes.chunks_exact(2) {
1598            let hi = (pair[0] as char)
1599                .to_digit(16)
1600                .expect("valid hex high nibble") as u8;
1601            let lo = (pair[1] as char)
1602                .to_digit(16)
1603                .expect("valid hex low nibble") as u8;
1604            out.push((hi << 4) | lo);
1605        }
1606        out
1607    }
1608
1609    #[test]
1610    fn noxtls_aes_gcm_matches_nist_vector_with_non_empty_aad() {
1611        let key = decode_hex("feffe9928665731c6d6a8f9467308308");
1612        let nonce = decode_hex("cafebabefacedbaddecaf888");
1613        let aad = decode_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2");
1614        let plaintext = decode_hex(
1615            "d9313225f88406e5a55909c5aff5269a\
1616             86a7a9531534f7da2e4c303d8a318a72\
1617             1c3c0c95956809532fcf0e2449a6b525\
1618             b16aedf5aa0de657ba637b39",
1619        );
1620        let expected_ciphertext = decode_hex(
1621            "42831ec2217774244b7221b784d0d49c\
1622             e3aa212f2c02a4e035c17e2329aca12e\
1623             21d514b25466931c7d8f6a5aac84aa05\
1624             1ba30b396a0aac973d58e091",
1625        );
1626        let expected_tag = decode_hex("5bc94fbc3221a5db94fae95ae7121a47");
1627
1628        let cipher = AesCipher::noxtls_new(&key).expect("valid AES-128 key");
1629        let (ciphertext, tag) =
1630            noxtls_aes_gcm_encrypt(&cipher, &nonce, &aad, &plaintext).expect("encrypt");
1631        assert_eq!(ciphertext, expected_ciphertext);
1632        assert_eq!(tag.as_slice(), expected_tag.as_slice());
1633
1634        let decrypted =
1635            noxtls_aes_gcm_decrypt(&cipher, &nonce, &aad, &ciphertext, &tag).expect("decrypt");
1636        assert_eq!(decrypted, plaintext);
1637    }
1638
1639    #[test]
1640    fn ghash_table_matches_reference_multiply() {
1641        let h = 0x66e94bd4ef8a2c3b884cfa59ca342b2e_u128;
1642        let table = GhashTable::new(h);
1643        for x in [
1644            0_u128,
1645            1_u128 << 127,
1646            1_u128 << 126,
1647            0x123456789abcdef01122334455667788_u128,
1648            0xffffffffffffffffffffffffffffffff_u128,
1649        ] {
1650            assert_eq!(table.mul(x), gf128_mul_slow(x, h));
1651        }
1652    }
1653}