From 90897dd902b2458b11cea945355515b724a7c6d0 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 29 Jun 2024 00:29:49 -0500 Subject: [PATCH] xaes: initial implementation --- aes-gcm/src/lib.rs | 6 ++ aes-gcm/src/xaes.rs | 127 ++++++++++++++++++++++++++++++++++++ aes-gcm/tests/common/mod.rs | 11 ++-- aes-gcm/tests/xaes256gcm.rs | 35 ++++++++++ 4 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 aes-gcm/src/xaes.rs create mode 100644 aes-gcm/tests/xaes256gcm.rs diff --git a/aes-gcm/src/lib.rs b/aes-gcm/src/lib.rs index 0b49f5c8..df971d29 100644 --- a/aes-gcm/src/lib.rs +++ b/aes-gcm/src/lib.rs @@ -108,11 +108,17 @@ //! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as //! [`aead::arrayvec::ArrayVec`]). +#[cfg(feature = "aes")] +mod xaes; + pub use aead::{self, AeadCore, AeadInPlace, Error, Key, KeyInit, KeySizeUser}; #[cfg(feature = "aes")] pub use aes; +#[cfg(feature = "aes")] +pub use xaes::XaesGcm256; + use cipher::{ array::{Array, ArraySize}, consts::{U0, U16}, diff --git a/aes-gcm/src/xaes.rs b/aes-gcm/src/xaes.rs new file mode 100644 index 00000000..77e857f9 --- /dev/null +++ b/aes-gcm/src/xaes.rs @@ -0,0 +1,127 @@ +use core::ops::{Div, Mul}; + +use aead::{array::Array, AeadCore, AeadInPlace, Error, Key, KeyInit, KeySizeUser}; +use cipher::{consts::U2, BlockCipherEncrypt, BlockSizeUser}; + +use crate::{Aes256, Aes256Gcm, Nonce, Tag}; + +/// XAES-256-GCM +#[derive(Clone)] +pub struct XaesGcm256 { + aes: Aes256, + k1: Block, +} + +type KeySize = ::KeySize; +type NonceSize = <::NonceSize as Mul>::Output; +type TagSize = ::TagSize; +type CiphertextOverhead = ::CiphertextOverhead; +type Block = Array::BlockSize>; + +/// Maximum length of plaintext. +pub const P_MAX: u64 = 1 << 36; + +/// Maximum length of associated data. +// pub const A_MAX: u64 = 1 << 61; +pub const A_MAX: u64 = 1 << 36; + +/// Maximum length of ciphertext. +pub const C_MAX: u64 = (1 << 36) + 16; + +impl AeadCore for XaesGcm256 { + type NonceSize = NonceSize; + type TagSize = TagSize; + type CiphertextOverhead = CiphertextOverhead; +} + +impl KeySizeUser for XaesGcm256 { + type KeySize = KeySize; +} + +impl KeyInit for XaesGcm256 { + // Implements step 1 and 2 of the spec. + fn new(key: &Key) -> Self { + let aes = Aes256::new(key); + + // L = AES-256ₖ(0¹²⁸) + let mut k1 = Block::default(); + aes.encrypt_block(&mut k1); + + // If MSB₁(L) = 0 then K1 = L << 1 Else K1 = (L << 1) ⊕ 0¹²⁰10000111 + let mut msb = 0; + for i in (0..k1.len()).rev() { + let new_msb = k1[i] >> 7; + k1[i] = (k1[i] << 1) | msb; + msb = new_msb; + } + + let b = k1.len() - 1; + k1[b] ^= msb * 0b10000111; + + Self { aes, k1 } + } +} + +impl AeadInPlace for XaesGcm256 { + fn encrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, Error> { + if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { + return Err(Error); + } + + let (n1, n) = nonce.split_ref::<>::Output>(); + let k = self.derive_key(n1); + Aes256Gcm::new(&k).encrypt_in_place_detached(n, associated_data, buffer) + } + + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { + return Err(Error); + } + + let (n1, n) = nonce.split_ref::<>::Output>(); + let k = self.derive_key(n1); + Aes256Gcm::new(&k).decrypt_in_place_detached(n, associated_data, buffer, tag) + } +} + +impl XaesGcm256 { + // Implements steps 3 - 5 of the spec. + fn derive_key(&self, n1: &Nonce<>::Output>) -> Key { + // M1 = 0x00 || 0x01 || X || 0x00 || N[:12] + let mut m1 = Block::default(); + m1[..4].copy_from_slice(&[0, 1, b'X', 0]); + m1[4..].copy_from_slice(n1); + + // M2 = 0x00 || 0x02 || X || 0x00 || N[:12] + let mut m2 = Block::default(); + m2[..4].copy_from_slice(&[0, 2, b'X', 0]); + m2[4..].copy_from_slice(n1); + + // Kₘ = AES-256ₖ(M1 ⊕ K1) + // Kₙ = AES-256ₖ(M2 ⊕ K1) + // Kₓ = Kₘ || Kₙ = AES-256ₖ(M1 ⊕ K1) || AES-256ₖ(M2 ⊕ K1) + let mut key: Key = Array::default(); + let (mut km, mut kn) = key.split_ref_mut::<>::Output>(); + for i in 0..km.len() { + km[i] = m1[i] ^ self.k1[i]; + } + for i in 0..kn.len() { + kn[i] = m2[i] ^ self.k1[i]; + } + + self.aes.encrypt_block(&mut km); + self.aes.encrypt_block(&mut kn); + key + } +} diff --git a/aes-gcm/tests/common/mod.rs b/aes-gcm/tests/common/mod.rs index fffc3d91..73ee8f14 100644 --- a/aes-gcm/tests/common/mod.rs +++ b/aes-gcm/tests/common/mod.rs @@ -4,7 +4,7 @@ #[derive(Debug)] pub struct TestVector { pub key: &'static K, - pub nonce: &'static [u8; 12], + pub nonce: &'static [u8], pub aad: &'static [u8], pub plaintext: &'static [u8], pub ciphertext: &'static [u8], @@ -27,8 +27,11 @@ macro_rules! tests { let cipher = <$aead>::new(key); let ciphertext = cipher.encrypt(nonce, payload).unwrap(); let (ct, tag) = ciphertext.split_at(ciphertext.len() - 16); - assert_eq!(vector.ciphertext, ct); - assert_eq!(vector.tag, tag); + assert_eq!( + vector.ciphertext, ct, + "ciphertext mismatch (expected != actual)" + ); + assert_eq!(vector.tag, tag, "tag mismatch (expected != actual)"); } } @@ -48,7 +51,7 @@ macro_rules! tests { let cipher = <$aead>::new(key); let plaintext = cipher.decrypt(nonce, payload).unwrap(); - assert_eq!(vector.plaintext, plaintext.as_slice()); + assert_eq!(vector.plaintext, plaintext.as_slice(), "plaintext mismatch"); } } diff --git a/aes-gcm/tests/xaes256gcm.rs b/aes-gcm/tests/xaes256gcm.rs new file mode 100644 index 00000000..0472adcc --- /dev/null +++ b/aes-gcm/tests/xaes256gcm.rs @@ -0,0 +1,35 @@ +//! XAES-256-GCM test vectors + +#![cfg(all(feature = "aes", feature = "alloc"))] + +#[macro_use] +mod common; + +use aes_gcm::aead::{array::Array, Aead, AeadInPlace, KeyInit, Payload}; +use aes_gcm::XaesGcm256; +use common::TestVector; +use hex_literal::hex; + +/// C2SP XAES-256-GCM test vectors +/// +/// +const TEST_VECTORS: &[TestVector<[u8; 32]>] = &[ + TestVector { + key: &hex!("0101010101010101010101010101010101010101010101010101010101010101"), + nonce: b"ABCDEFGHIJKLMNOPQRSTUVWX", + plaintext: b"XAES-256-GCM", + aad: b"", + ciphertext: &hex!("ce546ef63c9cc60765923609"), + tag: &hex!("b33a9a1974e96e52daf2fcf7075e2271"), + }, + TestVector { + key: &hex!("0303030303030303030303030303030303030303030303030303030303030303"), + nonce: b"ABCDEFGHIJKLMNOPQRSTUVWX", + plaintext: b"XAES-256-GCM", + aad: b"c2sp.org/XAES-256-GCM", + ciphertext: &hex!("986ec1832593df5443a17943"), + tag: &hex!("7fd083bf3fdb41abd740a21f71eb769d"), + }, +]; + +tests!(XaesGcm256, TEST_VECTORS);