1use 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#[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 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 pub fn encrypt_block(&self, block: &mut [u8; 16]) {
67 aes_encrypt_block_raw(&self.round_keys, self.rounds, block);
68 }
69
70 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#[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#[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
134pub 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
166pub 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
199pub 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
225pub fn noxtls_aes_cfb_apply(cipher: &AesCipher, iv: &[u8; 16], input: &[u8]) -> Vec<u8> {
235 noxtls_aes_cfb_encrypt(cipher, iv, input)
236}
237
238pub fn noxtls_aes_cfb_encrypt(cipher: &AesCipher, iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
248 aes_cfb_process(cipher, iv, plaintext, true)
249}
250
251pub fn noxtls_aes_cfb_decrypt(cipher: &AesCipher, iv: &[u8; 16], ciphertext: &[u8]) -> Vec<u8> {
261 aes_cfb_process(cipher, iv, ciphertext, false)
262}
263
264fn 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
301pub 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
325pub 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
351pub 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
382pub 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
401pub 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
496pub 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
517pub 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
615fn 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
645pub 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
664pub 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
683fn 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 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 out[partial_start..].copy_from_slice(&block[..rem]);
771
772 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 out[partial_start..].copy_from_slice(&c_m_minus_1[..rem]);
789
790 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
803fn 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
845fn rot_word(word: u32) -> u32 {
859 word.rotate_left(8)
860}
861
862fn 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
885fn 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
905fn sub_bytes(state: &mut [u8; 16]) {
919 for byte in state {
920 *byte = SBOX[usize::from(*byte)];
921 }
922}
923
924fn inv_sub_bytes(state: &mut [u8; 16]) {
938 for byte in state {
939 *byte = INV_SBOX[usize::from(*byte)];
940 }
941}
942
943fn 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
973fn 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
1003fn 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
1031fn 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#[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
1085fn 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
1107fn 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#[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
1258fn 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
1328fn 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
1368fn 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
1403fn 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
1433fn 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
1454fn 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
1477fn 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
1497fn 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
1522fn 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}