From 07acf8d33d9f0ca7757ac002fc763c8bd66b0512 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Mon, 9 Mar 2026 15:11:40 -0400 Subject: [PATCH 1/6] Rust wrapper: add rand_core trait support --- wrapper/rust/wolfssl-wolfcrypt/Cargo.lock | 7 +++ wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | 4 ++ wrapper/rust/wolfssl-wolfcrypt/Makefile | 13 +++-- wrapper/rust/wolfssl-wolfcrypt/src/random.rs | 28 +++++++++++ .../wolfssl-wolfcrypt/tests/test_random.rs | 50 +++++++++++++++++++ 5 files changed, 97 insertions(+), 5 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index cd58000817d..b91ba13718b 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -156,6 +156,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "regex" version = "1.11.2" @@ -290,5 +296,6 @@ name = "wolfssl-wolfcrypt" version = "1.2.0" dependencies = [ "bindgen", + "rand_core", "regex", ] diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index 61dee2bd282..ed59c271c88 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -12,6 +12,10 @@ readme = "README.md" [features] std = [] +rand_core = ["dep:rand_core"] + +[dependencies] +rand_core = { version = "0.10", optional = true, default-features = false } [build-dependencies] bindgen = "0.72.1" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index 7cda4189623..c534814c0e6 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,16 +1,19 @@ +FEATURES := rand_core +CARGO_FEATURE_FLAGS := --features $(FEATURES) + .PHONY: all all: - cargo build - cargo clippy - cargo doc + cargo build $(CARGO_FEATURE_FLAGS) + cargo clippy $(CARGO_FEATURE_FLAGS) + cargo doc $(CARGO_FEATURE_FLAGS) .PHONY: test test: - cargo test -- --test-threads=1 + cargo test $(CARGO_FEATURE_FLAGS) -- --test-threads=1 .PHONY: testfips testfips: - cargo test --lib --bins --tests -- --test-threads=1 + cargo test $(CARGO_FEATURE_FLAGS) --lib --bins --tests -- --test-threads=1 .PHONY: clean clean: diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs index fcfa5a4d2f9..7d3bb8a79ab 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs @@ -380,6 +380,34 @@ impl RNG { } } +/// Implement `rand_core::TryRng` for `RNG`, allowing it to be used anywhere +/// a standard Rust RNG is expected. +/// +/// `Error` is set to `Infallible` so that the blanket impls for `Rng` and +/// `CryptoRng` apply automatically. wolfSSL RNG failures cause a panic, which +/// is consistent with the infallible contract. +#[cfg(feature = "rand_core")] +impl rand_core::TryRng for RNG { + type Error = core::convert::Infallible; + + fn try_next_u32(&mut self) -> Result { + rand_core::utils::next_word_via_fill(self) + } + + fn try_next_u64(&mut self) -> Result { + rand_core::utils::next_word_via_fill(self) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { + self.generate_block(dest).expect("RNG failure"); + Ok(()) + } +} + +/// Mark `RNG` as a cryptographically secure random number generator. +#[cfg(feature = "rand_core")] +impl rand_core::TryCryptoRng for RNG {} + impl Drop for RNG { /// Safely free the underlying wolfSSL RNG context. /// diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs index 4b94ff091aa..bf7f1536a70 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs @@ -97,3 +97,53 @@ fn test_rng_reseed() { let seed = [1u8, 2, 3, 4]; rng.reseed(&seed).expect("Error with reseed()"); } + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_rand_core_fill_bytes() { + use rand_core::Rng; + let mut rng = RNG::new().expect("Failed to create RNG"); + let mut buf = [0u8; 32]; + rng.fill_bytes(&mut buf); + assert_ne!(buf, [0u8; 32]); +} + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_rand_core_try_fill_bytes() { + use rand_core::TryRng; + let mut rng = RNG::new().expect("Failed to create RNG"); + let mut buf = [0u8; 32]; + rng.try_fill_bytes(&mut buf).expect("Failed to try_fill_bytes"); + assert_ne!(buf, [0u8; 32]); +} + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_rand_core_next_u32() { + use rand_core::Rng; + let mut rng = RNG::new().expect("Failed to create RNG"); + // Generate several values and verify they aren't all zero + let v: u64 = (0..4).map(|_| rng.next_u32() as u64).sum(); + assert_ne!(v, 0); +} + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_rand_core_next_u64() { + use rand_core::Rng; + let mut rng = RNG::new().expect("Failed to create RNG"); + // Generate two values and verify they aren't all ones + let v1 = rng.next_u64(); + let v2 = rng.next_u64(); + assert_ne!(v1 & v2, u64::MAX); +} + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_is_crypto_rng() { + use rand_core::CryptoRng; + fn requires_crypto_rng(_: &R) {} + let rng = RNG::new().expect("Failed to create RNG"); + requires_crypto_rng(&rng); +} From 873bc05cde9f735268ac9b5177edbce660856d20 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Tue, 10 Mar 2026 08:32:18 -0400 Subject: [PATCH 2/6] Rust wrapper: add aead trait support --- wrapper/rust/wolfssl-wolfcrypt/Cargo.lock | 59 +++- wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | 5 + wrapper/rust/wolfssl-wolfcrypt/Makefile | 2 +- wrapper/rust/wolfssl-wolfcrypt/src/aes.rs | 332 ++++++++++++++++++ .../src/chacha20_poly1305.rs | 196 ++++++++++- .../rust/wolfssl-wolfcrypt/tests/test_aes.rs | 251 +++++++++++++ .../tests/test_chacha20_poly1305.rs | 226 ++++++++++++ 7 files changed, 1065 insertions(+), 6 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index b91ba13718b..cab8c54884c 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "blobby", + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -37,6 +48,12 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "blobby" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" + [[package]] name = "cexpr" version = "0.6.0" @@ -63,12 +80,33 @@ dependencies = [ "libloading", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "glob" version = "0.3.3" @@ -156,6 +194,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_core" version = "0.10.0" @@ -214,12 +258,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "windows-link" version = "0.1.3" @@ -295,7 +351,8 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" name = "wolfssl-wolfcrypt" version = "1.2.0" dependencies = [ + "aead", "bindgen", - "rand_core", + "rand_core 0.10.0", "regex", ] diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index ed59c271c88..08cd8e18439 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -13,9 +13,14 @@ readme = "README.md" [features] std = [] rand_core = ["dep:rand_core"] +aead = ["dep:aead"] [dependencies] rand_core = { version = "0.10", optional = true, default-features = false } +aead = { version = "0.5", optional = true, default-features = false } + +[dev-dependencies] +aead = { version = "0.5", features = ["alloc", "dev"] } [build-dependencies] bindgen = "0.72.1" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index c534814c0e6..e1db9ee9e77 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,4 +1,4 @@ -FEATURES := rand_core +FEATURES := rand_core,aead CARGO_FEATURE_FLAGS := --features $(FEATURES) .PHONY: all diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs b/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs index 5ca812b2a5c..0cad0c7f73d 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs @@ -28,6 +28,11 @@ Encryption Standard (AES) functionality. use crate::sys; use core::mem::{size_of_val, MaybeUninit}; +#[cfg(feature = "aead")] +use aead::{AeadCore, AeadInPlace, KeyInit, KeySizeUser}; +#[cfg(feature = "aead")] +use aead::generic_array::typenum::{U0, U12, U16, U32}; + #[cfg(aes_wc_block_size)] pub const AES_BLOCK_SIZE: usize = sys::WC_AES_BLOCK_SIZE as usize; #[cfg(not(aes_wc_block_size))] @@ -414,6 +419,169 @@ impl Drop for CCM { } } +// --------------------------------------------------------------------------- +// AES-CCM aead trait implementations +// --------------------------------------------------------------------------- + +/// Encrypt `buffer` in-place using AES-CCM (12-byte nonce, 16-byte tag). +#[cfg(all(aes_ccm, feature = "aead"))] +fn ccm_encrypt_in_place( + key: &[u8], + nonce: &[u8], + aad: &[u8], + buffer: &mut [u8], + tag: &mut [u8], +) -> Result<(), aead::Error> { + let mut ccm = CCM::new().map_err(|_| aead::Error)?; + ccm.init(key).map_err(|_| aead::Error)?; + // wolfCrypt CCM supports in-place operation (out == in). + let buf_ptr = buffer.as_mut_ptr(); + let in_ptr = buf_ptr as *const u8; + let rc = unsafe { + sys::wc_AesCcmEncrypt( + &mut ccm.ws_aes, + buf_ptr, in_ptr, buffer.len() as u32, + nonce.as_ptr(), nonce.len() as u32, + tag.as_mut_ptr(), tag.len() as u32, + aad.as_ptr(), aad.len() as u32, + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(()) +} + +/// Decrypt `buffer` in-place using AES-CCM and verify `tag`. +#[cfg(all(aes_ccm, feature = "aead"))] +fn ccm_decrypt_in_place( + key: &[u8], + nonce: &[u8], + aad: &[u8], + buffer: &mut [u8], + tag: &[u8], +) -> Result<(), aead::Error> { + let mut ccm = CCM::new().map_err(|_| aead::Error)?; + ccm.init(key).map_err(|_| aead::Error)?; + let buf_ptr = buffer.as_mut_ptr(); + let in_ptr = buf_ptr as *const u8; + let rc = unsafe { + sys::wc_AesCcmDecrypt( + &mut ccm.ws_aes, + buf_ptr, in_ptr, buffer.len() as u32, + nonce.as_ptr(), nonce.len() as u32, + tag.as_ptr(), tag.len() as u32, + aad.as_ptr(), aad.len() as u32, + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(()) +} + +/// AES-128-CCM authenticated encryption (12-byte nonce, 16-byte tag). +#[cfg(all(aes_ccm, feature = "aead"))] +pub struct Aes128Ccm { + key: [u8; 16], +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl KeySizeUser for Aes128Ccm { + type KeySize = U16; +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl AeadCore for Aes128Ccm { + type NonceSize = U12; + type TagSize = U16; + type CiphertextOverhead = U0; +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl KeyInit for Aes128Ccm { + fn new(key: &aead::Key) -> Self { + let mut k = [0u8; 16]; + k.copy_from_slice(key.as_ref()); + Aes128Ccm { key: k } + } +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl AeadInPlace for Aes128Ccm { + fn encrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, aead::Error> { + let mut tag = aead::Tag::::default(); + ccm_encrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_mut())?; + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> Result<(), aead::Error> { + ccm_decrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_ref()) + } +} + +/// AES-256-CCM authenticated encryption (12-byte nonce, 16-byte tag). +#[cfg(all(aes_ccm, feature = "aead"))] +pub struct Aes256Ccm { + key: [u8; 32], +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl KeySizeUser for Aes256Ccm { + type KeySize = U32; +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl AeadCore for Aes256Ccm { + type NonceSize = U12; + type TagSize = U16; + type CiphertextOverhead = U0; +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl KeyInit for Aes256Ccm { + fn new(key: &aead::Key) -> Self { + let mut k = [0u8; 32]; + k.copy_from_slice(key.as_ref()); + Aes256Ccm { key: k } + } +} + +#[cfg(all(aes_ccm, feature = "aead"))] +impl AeadInPlace for Aes256Ccm { + fn encrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, aead::Error> { + let mut tag = aead::Tag::::default(); + ccm_encrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_mut())?; + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> Result<(), aead::Error> { + ccm_decrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_ref()) + } +} + /// AES Cipher FeedBack (CFB) mode. /// /// # Example @@ -1394,6 +1562,170 @@ impl Drop for GCM { } } +// --------------------------------------------------------------------------- +// AES-GCM aead trait implementations +// --------------------------------------------------------------------------- + +/// Encrypt `buffer` in-place using AES-GCM (12-byte nonce, 16-byte tag). +/// +/// wolfCrypt's `wc_AesGcmEncrypt` supports in-place operation (out == in). +#[cfg(all(aes_gcm, feature = "aead"))] +fn gcm_encrypt_in_place( + key: &[u8], + nonce: &[u8], + aad: &[u8], + buffer: &mut [u8], + tag: &mut [u8], +) -> Result<(), aead::Error> { + let mut gcm = GCM::new().map_err(|_| aead::Error)?; + gcm.init(key).map_err(|_| aead::Error)?; + let buf_ptr = buffer.as_mut_ptr(); + let in_ptr = buf_ptr as *const u8; + let rc = unsafe { + sys::wc_AesGcmEncrypt( + &mut gcm.ws_aes, + buf_ptr, in_ptr, buffer.len() as u32, + nonce.as_ptr(), nonce.len() as u32, + tag.as_mut_ptr(), tag.len() as u32, + aad.as_ptr(), aad.len() as u32, + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(()) +} + +/// Decrypt `buffer` in-place using AES-GCM and verify `tag`. +#[cfg(all(aes_gcm, feature = "aead"))] +fn gcm_decrypt_in_place( + key: &[u8], + nonce: &[u8], + aad: &[u8], + buffer: &mut [u8], + tag: &[u8], +) -> Result<(), aead::Error> { + let mut gcm = GCM::new().map_err(|_| aead::Error)?; + gcm.init(key).map_err(|_| aead::Error)?; + let buf_ptr = buffer.as_mut_ptr(); + let in_ptr = buf_ptr as *const u8; + let rc = unsafe { + sys::wc_AesGcmDecrypt( + &mut gcm.ws_aes, + buf_ptr, in_ptr, buffer.len() as u32, + nonce.as_ptr(), nonce.len() as u32, + tag.as_ptr(), tag.len() as u32, + aad.as_ptr(), aad.len() as u32, + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(()) +} + +/// AES-128-GCM authenticated encryption (12-byte nonce, 16-byte tag). +#[cfg(all(aes_gcm, feature = "aead"))] +pub struct Aes128Gcm { + key: [u8; 16], +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl KeySizeUser for Aes128Gcm { + type KeySize = U16; +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl AeadCore for Aes128Gcm { + type NonceSize = U12; + type TagSize = U16; + type CiphertextOverhead = U0; +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl KeyInit for Aes128Gcm { + fn new(key: &aead::Key) -> Self { + let mut k = [0u8; 16]; + k.copy_from_slice(key.as_ref()); + Aes128Gcm { key: k } + } +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl AeadInPlace for Aes128Gcm { + fn encrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, aead::Error> { + let mut tag = aead::Tag::::default(); + gcm_encrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_mut())?; + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> Result<(), aead::Error> { + gcm_decrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_ref()) + } +} + +/// AES-256-GCM authenticated encryption (12-byte nonce, 16-byte tag). +#[cfg(all(aes_gcm, feature = "aead"))] +pub struct Aes256Gcm { + key: [u8; 32], +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl KeySizeUser for Aes256Gcm { + type KeySize = U32; +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl AeadCore for Aes256Gcm { + type NonceSize = U12; + type TagSize = U16; + type CiphertextOverhead = U0; +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl KeyInit for Aes256Gcm { + fn new(key: &aead::Key) -> Self { + let mut k = [0u8; 32]; + k.copy_from_slice(key.as_ref()); + Aes256Gcm { key: k } + } +} + +#[cfg(all(aes_gcm, feature = "aead"))] +impl AeadInPlace for Aes256Gcm { + fn encrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, aead::Error> { + let mut tag = aead::Tag::::default(); + gcm_encrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_mut())?; + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> Result<(), aead::Error> { + gcm_decrypt_in_place(&self.key, nonce.as_ref(), associated_data, buffer, tag.as_ref()) + } +} + /// AES Galois/Counter Mode (GCM) mode (streaming functionality). /// /// This struct provides streaming/chunking encryption and decryption diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs b/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs index c211a4401fa..f3c3ec67432 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs @@ -155,8 +155,7 @@ impl ChaCha20Poly1305 { return Err(rc); } let wc_ccp = unsafe { wc_ccp.assume_init() }; - let chacha20poly1305 = ChaCha20Poly1305 { wc_ccp }; - Ok(chacha20poly1305) + Ok(ChaCha20Poly1305 { wc_ccp }) } /// Update AAD (additional authenticated data). @@ -244,10 +243,95 @@ impl ChaCha20Poly1305 { } } -#[cfg(xchacha20_poly1305)] -pub struct XChaCha20Poly1305 { +// --------------------------------------------------------------------------- +// ChaCha20-Poly1305 aead trait implementations +// --------------------------------------------------------------------------- + +/// ChaCha20-Poly1305 AEAD instance holding a key for use with the +/// `aead::KeyInit` and `aead::AeadInPlace` traits. +#[cfg(feature = "aead")] +pub struct ChaCha20Poly1305Aead { + key: [u8; 32], +} + +#[cfg(feature = "aead")] +impl aead::KeySizeUser for ChaCha20Poly1305Aead { + type KeySize = aead::generic_array::typenum::U32; +} + +#[cfg(feature = "aead")] +impl aead::AeadCore for ChaCha20Poly1305Aead { + type NonceSize = aead::generic_array::typenum::U12; + type TagSize = aead::generic_array::typenum::U16; + type CiphertextOverhead = aead::generic_array::typenum::U0; +} + +#[cfg(feature = "aead")] +impl aead::KeyInit for ChaCha20Poly1305Aead { + fn new(key: &aead::Key) -> Self { + let mut k = [0u8; 32]; + k.copy_from_slice(key.as_ref()); + ChaCha20Poly1305Aead { key: k } + } } +#[cfg(feature = "aead")] +impl aead::AeadInPlace for ChaCha20Poly1305Aead { + fn encrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, aead::Error> { + let mut tag = aead::Tag::::default(); + // wc_ChaCha20Poly1305_Encrypt supports in-place (out == in). + let buf_ptr = buffer.as_mut_ptr(); + let in_ptr = buf_ptr as *const u8; + let nonce_bytes: &[u8] = nonce; + let tag_bytes: &mut [u8] = &mut tag; + let rc = unsafe { + sys::wc_ChaCha20Poly1305_Encrypt( + self.key.as_ptr(), nonce_bytes.as_ptr(), + associated_data.as_ptr(), associated_data.len() as u32, + in_ptr, buffer.len() as u32, + buf_ptr, tag_bytes.as_mut_ptr(), + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> Result<(), aead::Error> { + let buf_ptr = buffer.as_mut_ptr(); + let in_ptr = buf_ptr as *const u8; + let nonce_bytes: &[u8] = nonce; + let tag_bytes: &[u8] = tag; + let rc = unsafe { + sys::wc_ChaCha20Poly1305_Decrypt( + self.key.as_ptr(), nonce_bytes.as_ptr(), + associated_data.as_ptr(), associated_data.len() as u32, + in_ptr, buffer.len() as u32, + tag_bytes.as_ptr(), buf_ptr, + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(()) + } +} + +#[cfg(xchacha20_poly1305)] +pub struct XChaCha20Poly1305; + #[cfg(xchacha20_poly1305)] impl XChaCha20Poly1305 { /// Key size for XChaCha20-Poly1305 stream cipher. @@ -340,3 +424,107 @@ impl XChaCha20Poly1305 { Ok(()) } } + +// --------------------------------------------------------------------------- +// XChaCha20-Poly1305 aead trait implementations +// --------------------------------------------------------------------------- + +/// XChaCha20-Poly1305 AEAD instance holding a key for use with the +/// `aead::KeyInit` and `aead::AeadInPlace` traits. +#[cfg(all(xchacha20_poly1305, feature = "aead"))] +pub struct XChaCha20Poly1305Aead { + key: [u8; 32], +} + +#[cfg(all(xchacha20_poly1305, feature = "aead"))] +impl aead::KeySizeUser for XChaCha20Poly1305Aead { + type KeySize = aead::generic_array::typenum::U32; +} + +#[cfg(all(xchacha20_poly1305, feature = "aead"))] +impl aead::AeadCore for XChaCha20Poly1305Aead { + type NonceSize = aead::generic_array::typenum::U24; + type TagSize = aead::generic_array::typenum::U16; + type CiphertextOverhead = aead::generic_array::typenum::U0; +} + +#[cfg(all(xchacha20_poly1305, feature = "aead"))] +impl aead::KeyInit for XChaCha20Poly1305Aead { + fn new(key: &aead::Key) -> Self { + let mut k = [0u8; 32]; + k.copy_from_slice(key.as_ref()); + XChaCha20Poly1305Aead { key: k } + } +} + +#[cfg(all(xchacha20_poly1305, feature = "aead"))] +impl aead::AeadInPlace for XChaCha20Poly1305Aead { + fn encrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result, aead::Error> { + // wc_XChaCha20Poly1305_Encrypt writes ciphertext + 16-byte tag into a + // single output buffer. Use a stack buffer to hold both, then split + // the tag out and copy the ciphertext back over the caller's buffer. + const MAX_INLINE: usize = 4096; + if buffer.len() > MAX_INLINE { + return Err(aead::Error); + } + let out_len = buffer.len() + 16; + let mut out_buf = [0u8; MAX_INLINE + 16]; + let nonce_bytes: &[u8] = nonce; + let rc = unsafe { + sys::wc_XChaCha20Poly1305_Encrypt( + out_buf.as_mut_ptr(), out_len, + buffer.as_ptr(), buffer.len(), + associated_data.as_ptr(), associated_data.len(), + nonce_bytes.as_ptr(), nonce_bytes.len(), + self.key.as_ptr(), self.key.len(), + ) + }; + if rc != 0 { + return Err(aead::Error); + } + buffer.copy_from_slice(&out_buf[..buffer.len()]); + let mut tag = aead::Tag::::default(); + let tag_bytes: &mut [u8] = &mut tag; + tag_bytes.copy_from_slice(&out_buf[buffer.len()..out_len]); + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> Result<(), aead::Error> { + // wc_XChaCha20Poly1305_Decrypt expects the auth tag appended after the + // ciphertext. Build a combined [ciphertext | tag] buffer on the stack. + const MAX_INLINE: usize = 4096; + if buffer.len() > MAX_INLINE { + return Err(aead::Error); + } + let mut in_buf = [0u8; MAX_INLINE + 16]; + let in_len = buffer.len() + 16; + in_buf[..buffer.len()].copy_from_slice(buffer); + let tag_bytes: &[u8] = tag; + in_buf[buffer.len()..in_len].copy_from_slice(tag_bytes); + let nonce_bytes: &[u8] = nonce; + let rc = unsafe { + sys::wc_XChaCha20Poly1305_Decrypt( + buffer.as_mut_ptr(), buffer.len(), + in_buf.as_ptr(), in_len, + associated_data.as_ptr(), associated_data.len(), + nonce_bytes.as_ptr(), nonce_bytes.len(), + self.key.as_ptr(), self.key.len(), + ) + }; + if rc != 0 { + return Err(aead::Error); + } + Ok(()) + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs index 522a875cc2d..cc916fabde6 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs @@ -863,3 +863,254 @@ fn test_xtsstream_big_msg() { assert_eq!(plain_out, BIG_MSG); } + +// --------------------------------------------------------------------------- +// AES aead trait implementations +// --------------------------------------------------------------------------- + +#[cfg(feature = "aead")] +use aead::{Aead, AeadInPlace, KeyInit, Payload}; + +/// NIST SP 800-38D, Test Case 2: +/// Key = 00000000000000000000000000000000 +/// IV = 000000000000000000000000 +/// PT = 00000000000000000000000000000000 +/// AAD = (empty) +/// CT = 0388dace60b6a392f328c2b971b2fe78 +/// Tag = ab6e47d42cec13bdf53a67b21257bddf +#[test] +#[cfg(all(feature = "aead", aes_gcm))] +fn test_aes128gcm_nist_tc2_encrypt() { + let key = [0u8; 16]; + let nonce = [0u8; 12]; + let expected_ciphertext = [ + 0x03u8, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, + 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78, + ]; + let expected_tag = [ + 0xabu8, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, + 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf, + ]; + + let cipher = Aes128Gcm::new_from_slice(&key).unwrap(); + let nonce_arr: aead::Nonce = nonce.into(); + let mut buffer = [0u8; 16]; + let tag = cipher + .encrypt_in_place_detached(&nonce_arr, &[], &mut buffer) + .expect("AES-128-GCM encrypt failed"); + + assert_eq!(buffer, expected_ciphertext); + assert_eq!(&tag[..], &expected_tag); +} + +#[test] +#[cfg(all(feature = "aead", aes_gcm))] +fn test_aes128gcm_nist_tc2_decrypt() { + let key = [0u8; 16]; + let nonce = [0u8; 12]; + let mut ciphertext = [ + 0x03u8, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, + 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78, + ]; + let tag_bytes = [ + 0xabu8, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, + 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf, + ]; + + let cipher = Aes128Gcm::new_from_slice(&key).unwrap(); + let nonce_arr: aead::Nonce = nonce.into(); + let tag: aead::Tag = tag_bytes.into(); + cipher + .decrypt_in_place_detached(&nonce_arr, &[], &mut ciphertext, &tag) + .expect("AES-128-GCM decrypt failed"); + + assert_eq!(ciphertext, [0u8; 16]); +} + +/// Test AES-128-GCM roundtrip using the `aead::Aead` blanket impl. +#[test] +#[cfg(all(feature = "aead", aes_gcm))] +fn test_aes128gcm_aead_roundtrip() { + let key = [0x42u8; 16]; + let nonce_bytes = [0x11u8; 12]; + let aad = b"associated data"; + let plaintext = b"Hello, AEAD world!"; + + let cipher = Aes128Gcm::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let ciphertext = cipher + .encrypt(&nonce, Payload { msg: plaintext, aad }) + .expect("AES-128-GCM Aead::encrypt failed"); + + let recovered = cipher + .decrypt(&nonce, Payload { msg: &ciphertext, aad }) + .expect("AES-128-GCM Aead::decrypt failed"); + + assert_eq!(recovered, plaintext); +} + +/// Verify that decryption rejects a tampered tag. +#[test] +#[cfg(all(feature = "aead", aes_gcm))] +fn test_aes128gcm_reject_bad_tag() { + let key = [0u8; 16]; + let nonce_bytes = [0u8; 12]; + let plaintext = b"some plaintext!"; + + let cipher = Aes128Gcm::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let mut ct = cipher.encrypt(&nonce, plaintext.as_ref()).expect("encrypt failed"); + let last = ct.len() - 1; + ct[last] ^= 0xff; + assert!(cipher.decrypt(&nonce, ct.as_slice()).is_err()); +} + +/// NIST SP 800-38D, Test Case 14 (256-bit key): +/// Key = feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308 +/// IV = cafebabefacedbaddecaf888 +/// PT = d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a7 +/// 21c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39 (60 B) +/// AAD = feedfacedeadbeeffeedfacedeadbeefabaddad2 +/// CT = 522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1a +/// a8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0a (60 B) +/// Tag = 76fc6ece0f4e1768cddf8853bb2d551b +#[test] +#[cfg(all(feature = "aead", aes_gcm))] +fn test_aes256gcm_nist_tc14_encrypt() { + let key = [ + 0xfeu8, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08, + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08, + ]; + let nonce = [ + 0xcau8, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, + 0xde, 0xca, 0xf8, 0x88, + ]; + let aad = [ + 0xfeu8, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xab, 0xad, 0xda, 0xd2, + ]; + let plaintext = [ + 0xd9u8, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, + 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, + 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, + 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, + 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, + 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, + 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, + 0xba, 0x63, 0x7b, 0x39, + ]; + let expected_ciphertext = [ + 0x52u8, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07, + 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d, + 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9, + 0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa, + 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d, + 0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38, + 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a, + 0xbc, 0xc9, 0xf6, 0x62, + ]; + let expected_tag = [ + 0x76u8, 0xfc, 0x6e, 0xce, 0x0f, 0x4e, 0x17, 0x68, + 0xcd, 0xdf, 0x88, 0x53, 0xbb, 0x2d, 0x55, 0x1b, + ]; + + let cipher = Aes256Gcm::new_from_slice(&key).unwrap(); + let nonce_arr: aead::Nonce = nonce.into(); + let mut buffer = plaintext; + let tag = cipher + .encrypt_in_place_detached(&nonce_arr, &aad, &mut buffer) + .expect("AES-256-GCM encrypt failed"); + + assert_eq!(buffer, expected_ciphertext); + assert_eq!(&tag[..], &expected_tag); +} + +/// Roundtrip test for AES-256-GCM using `aead::Aead`. +#[test] +#[cfg(all(feature = "aead", aes_gcm))] +fn test_aes256gcm_aead_roundtrip() { + let key = [0xabu8; 32]; + let nonce_bytes = [0xbcu8; 12]; + let aad = b"test aad"; + let plaintext = b"AES-256-GCM roundtrip test"; + + let cipher = Aes256Gcm::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let ciphertext = cipher + .encrypt(&nonce, Payload { msg: plaintext, aad }) + .expect("encrypt failed"); + + let recovered = cipher + .decrypt(&nonce, Payload { msg: &ciphertext, aad }) + .expect("decrypt failed"); + + assert_eq!(recovered, plaintext); +} + +/// Roundtrip test for AES-128-CCM using `aead::Aead`. +#[test] +#[cfg(all(feature = "aead", aes_ccm))] +fn test_aes128ccm_aead_roundtrip() { + let key = [0x01u8; 16]; + let nonce_bytes = [0x02u8; 12]; + let aad = b"ccm aad"; + let plaintext = b"AES-128-CCM plaintext!"; + + let cipher = Aes128Ccm::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let ciphertext = cipher + .encrypt(&nonce, Payload { msg: plaintext, aad }) + .expect("AES-128-CCM encrypt failed"); + + let recovered = cipher + .decrypt(&nonce, Payload { msg: &ciphertext, aad }) + .expect("AES-128-CCM decrypt failed"); + + assert_eq!(recovered, plaintext); +} + +/// Verify that AES-128-CCM decryption rejects a tampered ciphertext. +#[test] +#[cfg(all(feature = "aead", aes_ccm))] +fn test_aes128ccm_reject_tampered() { + let key = [0x01u8; 16]; + let nonce_bytes = [0x02u8; 12]; + let plaintext = b"AES-128-CCM tamper test!"; + + let cipher = Aes128Ccm::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let mut ct = cipher.encrypt(&nonce, plaintext.as_ref()).expect("encrypt failed"); + ct[0] ^= 0x01; + assert!(cipher.decrypt(&nonce, ct.as_slice()).is_err()); +} + +/// Roundtrip test for AES-256-CCM using `aead::Aead`. +#[test] +#[cfg(all(feature = "aead", aes_ccm))] +fn test_aes256ccm_aead_roundtrip() { + let key = [0xddu8; 32]; + let nonce_bytes = [0xeeu8; 12]; + let aad = b"aes-256-ccm test"; + let plaintext = b"AES-256-CCM plaintext data"; + + let cipher = Aes256Ccm::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let ciphertext = cipher + .encrypt(&nonce, Payload { msg: plaintext, aad }) + .expect("AES-256-CCM encrypt failed"); + + let recovered = cipher + .decrypt(&nonce, Payload { msg: &ciphertext, aad }) + .expect("AES-256-CCM decrypt failed"); + + assert_eq!(recovered, plaintext); +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_chacha20_poly1305.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_chacha20_poly1305.rs index 3f6698d6ba9..ac18fd21b09 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_chacha20_poly1305.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_chacha20_poly1305.rs @@ -273,3 +273,229 @@ fn test_xchacha20_poly1305() { XChaCha20Poly1305::decrypt(&key, &iv, &aad, &ciphertext_buffer, &mut plaintext_buffer).expect("Error with decrypt()"); assert_eq!(plaintext_buffer, PLAINTEXT); } + +// --------------------------------------------------------------------------- +// ChaCha20-Poly1305 aead trait implementations +// --------------------------------------------------------------------------- + +#[cfg(feature = "aead")] +use aead::{Aead, AeadInPlace, KeyInit, Payload}; + +/// RFC 8439, Section 2.8.2 test vector. +/// +/// Key = 808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f +/// IV = 070000004041424344454647 +/// AAD = 50515253c0c1c2c3c4c5c6c7 +/// PT = 4c61646965732061... +/// Tag = 1ae10b594f09e26a7e902ecbd0600691 +#[test] +#[cfg(feature = "aead")] +fn test_chacha20poly1305_rfc8439_encrypt() { + let key = [ + 0x80u8, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + ]; + let nonce = [ + 0x07u8, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, + ]; + let aad = [ + 0x50u8, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, + ]; + let mut plaintext = [ + 0x4cu8, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, + 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, + 0x74, 0x2e, + ]; + let expected_ciphertext = [ + 0xd3u8, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, + 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, + 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, + 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, + 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, + 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, + 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, + 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, + 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, + 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, + 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, + 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, + 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, + 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, + 0x61, 0x16, + ]; + let expected_tag = [ + 0x1au8, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, + 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91, + ]; + + let cipher = ChaCha20Poly1305Aead::new_from_slice(&key).unwrap(); + let nonce_arr: aead::Nonce = nonce.into(); + let tag = cipher + .encrypt_in_place_detached(&nonce_arr, &aad, &mut plaintext) + .expect("ChaCha20-Poly1305 encrypt failed"); + + assert_eq!(plaintext, expected_ciphertext); + assert_eq!(&tag[..], &expected_tag); +} + +/// Roundtrip test for ChaCha20-Poly1305 using `aead::Aead`. +#[test] +#[cfg(feature = "aead")] +fn test_chacha20poly1305_aead_roundtrip() { + let key = [0x55u8; 32]; + let nonce_bytes = [0x66u8; 12]; + let aad = b"chacha20 aad"; + let plaintext = b"ChaCha20-Poly1305 roundtrip"; + + let cipher = ChaCha20Poly1305Aead::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let ciphertext = cipher + .encrypt(&nonce, Payload { msg: plaintext, aad }) + .expect("encrypt failed"); + + let recovered = cipher + .decrypt(&nonce, Payload { msg: &ciphertext, aad }) + .expect("decrypt failed"); + + assert_eq!(recovered, plaintext); +} + +/// Verify that ChaCha20-Poly1305 rejects a tampered message. +#[test] +#[cfg(feature = "aead")] +fn test_chacha20poly1305_reject_tampered() { + let key = [0x77u8; 32]; + let nonce_bytes = [0x88u8; 12]; + let plaintext = b"tamper me!"; + + let cipher = ChaCha20Poly1305Aead::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let mut ct = cipher.encrypt(&nonce, plaintext.as_ref()).expect("encrypt failed"); + ct[0] ^= 0x01; + assert!(cipher.decrypt(&nonce, ct.as_slice()).is_err()); +} + +/// Roundtrip test for XChaCha20-Poly1305 using `aead::Aead`. +#[test] +#[cfg(all(feature = "aead", xchacha20_poly1305))] +fn test_xchacha20poly1305_aead_roundtrip() { + let key = [0xaau8; 32]; + let nonce_bytes = [0xbbu8; 24]; + let aad = b"xchacha20 aad"; + let plaintext = b"XChaCha20-Poly1305 roundtrip"; + + let cipher = XChaCha20Poly1305Aead::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let ciphertext = cipher + .encrypt(&nonce, Payload { msg: plaintext, aad }) + .expect("XChaCha20-Poly1305 encrypt failed"); + + let recovered = cipher + .decrypt(&nonce, Payload { msg: &ciphertext, aad }) + .expect("XChaCha20-Poly1305 decrypt failed"); + + assert_eq!(recovered, plaintext); +} + +/// RFC 8439-based XChaCha20-Poly1305 known-answer test. +#[test] +#[cfg(all(feature = "aead", xchacha20_poly1305))] +fn test_xchacha20poly1305_known_answer() { + let key = [ + 0x80u8, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + ]; + let nonce = [ + 0x40u8, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + ]; + let aad = [ + 0x50u8, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, + ]; + let mut plaintext = [ + 0x4cu8, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, + 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, + 0x74, 0x2e, + ]; + let expected_ciphertext = [ + 0xbdu8, 0x6d, 0x17, 0x9d, 0x3e, 0x83, 0xd4, 0x3b, + 0x95, 0x76, 0x57, 0x94, 0x93, 0xc0, 0xe9, 0x39, + 0x57, 0x2a, 0x17, 0x00, 0x25, 0x2b, 0xfa, 0xcc, + 0xbe, 0xd2, 0x90, 0x2c, 0x21, 0x39, 0x6c, 0xbb, + 0x73, 0x1c, 0x7f, 0x1b, 0x0b, 0x4a, 0xa6, 0x44, + 0x0b, 0xf3, 0xa8, 0x2f, 0x4e, 0xda, 0x7e, 0x39, + 0xae, 0x64, 0xc6, 0x70, 0x8c, 0x54, 0xc2, 0x16, + 0xcb, 0x96, 0xb7, 0x2e, 0x12, 0x13, 0xb4, 0x52, + 0x2f, 0x8c, 0x9b, 0xa4, 0x0d, 0xb5, 0xd9, 0x45, + 0xb1, 0x1b, 0x69, 0xb9, 0x82, 0xc1, 0xbb, 0x9e, + 0x3f, 0x3f, 0xac, 0x2b, 0xc3, 0x69, 0x48, 0x8f, + 0x76, 0xb2, 0x38, 0x35, 0x65, 0xd3, 0xff, 0xf9, + 0x21, 0xf9, 0x66, 0x4c, 0x97, 0x63, 0x7d, 0xa9, + 0x76, 0x88, 0x12, 0xf6, 0x15, 0xc6, 0x8b, 0x13, + 0xb5, 0x2e, + ]; + let expected_tag = [ + 0xc0u8, 0x87, 0x59, 0x24, 0xc1, 0xc7, 0x98, 0x79, + 0x47, 0xde, 0xaf, 0xd8, 0x78, 0x0a, 0xcf, 0x49, + ]; + + let cipher = XChaCha20Poly1305Aead::new_from_slice(&key).unwrap(); + let nonce_arr: aead::Nonce = nonce.into(); + let tag = cipher + .encrypt_in_place_detached(&nonce_arr, &aad, &mut plaintext) + .expect("XChaCha20-Poly1305 encrypt failed"); + + assert_eq!(plaintext, expected_ciphertext); + assert_eq!(&tag[..], &expected_tag); +} + +/// Verify that XChaCha20-Poly1305 decryption rejects a tampered ciphertext. +#[test] +#[cfg(all(feature = "aead", xchacha20_poly1305))] +fn test_xchacha20poly1305_reject_tampered() { + let key = [0x55u8; 32]; + let nonce_bytes = [0x66u8; 24]; + let plaintext = b"XChaCha tamper test"; + + let cipher = XChaCha20Poly1305Aead::new_from_slice(&key).unwrap(); + let nonce: aead::Nonce = nonce_bytes.into(); + + let mut ct = cipher.encrypt(&nonce, plaintext.as_ref()).expect("encrypt failed"); + ct[0] ^= 0x01; + assert!(cipher.decrypt(&nonce, ct.as_slice()).is_err()); +} From 23cb7ae30c2305ac4e37f506bd2b6cffa8866cc3 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Fri, 13 Mar 2026 15:12:48 -0400 Subject: [PATCH 3/6] Rust wrapper: add cipher trait support --- wrapper/rust/wolfssl-wolfcrypt/Cargo.lock | 40 +- wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | 3 + wrapper/rust/wolfssl-wolfcrypt/Makefile | 2 +- wrapper/rust/wolfssl-wolfcrypt/src/aes.rs | 1019 +++++++++++++++++ .../rust/wolfssl-wolfcrypt/tests/test_aes.rs | 309 +++++ 5 files changed, 1371 insertions(+), 2 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index cab8c54884c..3a6dbadd24d 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "blobby", - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -69,6 +69,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "crypto-common 0.2.1", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -91,6 +101,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "either" version = "1.15.0" @@ -113,6 +132,24 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hybrid-array" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +dependencies = [ + "typenum", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "itertools" version = "0.13.0" @@ -353,6 +390,7 @@ version = "1.2.0" dependencies = [ "aead", "bindgen", + "cipher", "rand_core 0.10.0", "regex", ] diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index 08cd8e18439..7dca3510e0f 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -14,13 +14,16 @@ readme = "README.md" std = [] rand_core = ["dep:rand_core"] aead = ["dep:aead"] +cipher = ["dep:cipher"] [dependencies] rand_core = { version = "0.10", optional = true, default-features = false } aead = { version = "0.5", optional = true, default-features = false } +cipher = { version = "0.5", optional = true, default-features = false } [dev-dependencies] aead = { version = "0.5", features = ["alloc", "dev"] } +cipher = "0.5" [build-dependencies] bindgen = "0.72.1" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index e1db9ee9e77..37dc9a8579f 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,4 +1,4 @@ -FEATURES := rand_core,aead +FEATURES := rand_core,aead,cipher CARGO_FEATURE_FLAGS := --features $(FEATURES) .PHONY: all diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs b/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs index 0cad0c7f73d..a85d1bab0b9 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs @@ -30,9 +30,23 @@ use core::mem::{size_of_val, MaybeUninit}; #[cfg(feature = "aead")] use aead::{AeadCore, AeadInPlace, KeyInit, KeySizeUser}; + #[cfg(feature = "aead")] use aead::generic_array::typenum::{U0, U12, U16, U32}; +#[cfg(all(feature = "cipher", not(feature = "aead")))] +use cipher::typenum::consts::{U16, U32}; + +#[cfg(feature = "cipher")] +use cipher::typenum::consts::U24; + +#[cfg(feature = "cipher")] +use cipher::{ + BlockModeDecBackend, BlockModeDecClosure, BlockModeDecrypt, + BlockModeEncBackend, BlockModeEncClosure, BlockModeEncrypt, + IvSizeUser, KeyIvInit, ParBlocksSizeUser, StreamCipher, StreamCipherError, +}; + #[cfg(aes_wc_block_size)] pub const AES_BLOCK_SIZE: usize = sys::WC_AES_BLOCK_SIZE as usize; #[cfg(not(aes_wc_block_size))] @@ -2865,6 +2879,1011 @@ impl Drop for XTSStream { } } +// --------------------------------------------------------------------------- +// AES-ECB cipher trait implementations +// --------------------------------------------------------------------------- + +/// AES-128 ECB block cipher (encryption) implementing [`cipher::BlockModeEncrypt`]. +/// +/// The key schedule is computed once during construction via +/// [`cipher::KeyInit::new`] or [`cipher::KeyInit::new_from_slice`]. +#[cfg(all(aes_ecb, feature = "cipher"))] +pub struct Aes128EcbEnc { + inner: ECB, +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes128EcbEnc { + type KeySize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128EcbEnc { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeyInit for Aes128EcbEnc { + fn new(key: &cipher::Key) -> Self { + let mut ecb = ECB::new().expect("wc_AesInit failed"); + ecb.init_encrypt(key.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ecb } + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +struct Aes128EcbEncBackend<'a>(&'a mut Aes128EcbEnc); + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128EcbEncBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl ParBlocksSizeUser for Aes128EcbEncBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeEncBackend for Aes128EcbEncBackend<'_> { + fn encrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.encrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesEcbEncrypt failed"); + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeEncrypt for Aes128EcbEnc { + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure) { + f.call(&mut Aes128EcbEncBackend(self)); + } +} + +/// AES-192 ECB block cipher (encryption) implementing [`cipher::BlockModeEncrypt`]. +#[cfg(all(aes_ecb, feature = "cipher"))] +pub struct Aes192EcbEnc { + inner: ECB, +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes192EcbEnc { + type KeySize = U24; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192EcbEnc { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeyInit for Aes192EcbEnc { + fn new(key: &cipher::Key) -> Self { + let mut ecb = ECB::new().expect("wc_AesInit failed"); + ecb.init_encrypt(key.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ecb } + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +struct Aes192EcbEncBackend<'a>(&'a mut Aes192EcbEnc); + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192EcbEncBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl ParBlocksSizeUser for Aes192EcbEncBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeEncBackend for Aes192EcbEncBackend<'_> { + fn encrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.encrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesEcbEncrypt failed"); + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeEncrypt for Aes192EcbEnc { + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure) { + f.call(&mut Aes192EcbEncBackend(self)); + } +} + +/// AES-256 ECB block cipher (encryption) implementing [`cipher::BlockModeEncrypt`]. +#[cfg(all(aes_ecb, feature = "cipher"))] +pub struct Aes256EcbEnc { + inner: ECB, +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes256EcbEnc { + type KeySize = U32; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256EcbEnc { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeyInit for Aes256EcbEnc { + fn new(key: &cipher::Key) -> Self { + let mut ecb = ECB::new().expect("wc_AesInit failed"); + ecb.init_encrypt(key.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ecb } + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +struct Aes256EcbEncBackend<'a>(&'a mut Aes256EcbEnc); + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256EcbEncBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl ParBlocksSizeUser for Aes256EcbEncBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeEncBackend for Aes256EcbEncBackend<'_> { + fn encrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.encrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesEcbEncrypt failed"); + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeEncrypt for Aes256EcbEnc { + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure) { + f.call(&mut Aes256EcbEncBackend(self)); + } +} + +/// AES-128 ECB block cipher (decryption) implementing [`cipher::BlockModeDecrypt`]. +/// +/// The key schedule is computed once during construction via +/// [`cipher::KeyInit::new`] or [`cipher::KeyInit::new_from_slice`]. +#[cfg(all(aes_ecb, feature = "cipher"))] +pub struct Aes128EcbDec { + inner: ECB, +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes128EcbDec { + type KeySize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128EcbDec { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeyInit for Aes128EcbDec { + fn new(key: &cipher::Key) -> Self { + let mut ecb = ECB::new().expect("wc_AesInit failed"); + ecb.init_decrypt(key.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ecb } + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +struct Aes128EcbDecBackend<'a>(&'a mut Aes128EcbDec); + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128EcbDecBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl ParBlocksSizeUser for Aes128EcbDecBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeDecBackend for Aes128EcbDecBackend<'_> { + fn decrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.decrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesEcbDecrypt failed"); + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeDecrypt for Aes128EcbDec { + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure) { + f.call(&mut Aes128EcbDecBackend(self)); + } +} + +/// AES-192 ECB block cipher (decryption) implementing [`cipher::BlockModeDecrypt`]. +#[cfg(all(aes_ecb, feature = "cipher"))] +pub struct Aes192EcbDec { + inner: ECB, +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes192EcbDec { + type KeySize = U24; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192EcbDec { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeyInit for Aes192EcbDec { + fn new(key: &cipher::Key) -> Self { + let mut ecb = ECB::new().expect("wc_AesInit failed"); + ecb.init_decrypt(key.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ecb } + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +struct Aes192EcbDecBackend<'a>(&'a mut Aes192EcbDec); + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192EcbDecBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl ParBlocksSizeUser for Aes192EcbDecBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeDecBackend for Aes192EcbDecBackend<'_> { + fn decrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.decrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesEcbDecrypt failed"); + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeDecrypt for Aes192EcbDec { + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure) { + f.call(&mut Aes192EcbDecBackend(self)); + } +} + +/// AES-256 ECB block cipher (decryption) implementing [`cipher::BlockModeDecrypt`]. +#[cfg(all(aes_ecb, feature = "cipher"))] +pub struct Aes256EcbDec { + inner: ECB, +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes256EcbDec { + type KeySize = U32; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256EcbDec { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::KeyInit for Aes256EcbDec { + fn new(key: &cipher::Key) -> Self { + let mut ecb = ECB::new().expect("wc_AesInit failed"); + ecb.init_decrypt(key.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ecb } + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +struct Aes256EcbDecBackend<'a>(&'a mut Aes256EcbDec); + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256EcbDecBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl ParBlocksSizeUser for Aes256EcbDecBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeDecBackend for Aes256EcbDecBackend<'_> { + fn decrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.decrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesEcbDecrypt failed"); + } +} + +#[cfg(all(aes_ecb, feature = "cipher"))] +impl BlockModeDecrypt for Aes256EcbDec { + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure) { + f.call(&mut Aes256EcbDecBackend(self)); + } +} + +// --------------------------------------------------------------------------- +// AES-CTR cipher trait implementations +// --------------------------------------------------------------------------- + +/// AES-128 CTR stream cipher implementing [`cipher::StreamCipher`]. +/// +/// wolfCrypt AES-CTR supports in-place operation, so the in/out pointers from +/// the [`cipher::inout::InOutBuf`] are passed directly to `wc_AesCtrEncrypt`. +#[cfg(all(aes_ctr, feature = "cipher"))] +pub struct Aes128Ctr { + inner: CTR, +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl cipher::KeySizeUser for Aes128Ctr { + type KeySize = U16; +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl IvSizeUser for Aes128Ctr { + type IvSize = U16; +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl KeyIvInit for Aes128Ctr { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut ctr = CTR::new().expect("wc_AesInit failed"); + ctr.init(key.as_ref(), iv.as_ref()).expect("wc_AesSetKeyDirect failed"); + Self { inner: ctr } + } +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl StreamCipher for Aes128Ctr { + fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> { + Ok(()) + } + + fn unchecked_apply_keystream_inout(&mut self, mut buf: cipher::InOutBuf<'_, '_, u8>) { + let len = buf.len(); + if len == 0 { return; } + // wolfCrypt AES-CTR supports in-place operation (out == in). + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + // SAFETY: CTR in-place is valid; raw ptrs used to avoid aliasing rules. + let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; + let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; + self.inner.encrypt(in_slice, out_slice).expect("wc_AesCtrEncrypt failed"); + } + + fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { + buf.fill(0); + self.unchecked_apply_keystream_inout(buf.into()); + } +} + +/// AES-192 CTR stream cipher implementing [`cipher::StreamCipher`]. +#[cfg(all(aes_ctr, feature = "cipher"))] +pub struct Aes192Ctr { + inner: CTR, +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl cipher::KeySizeUser for Aes192Ctr { + type KeySize = U24; +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl IvSizeUser for Aes192Ctr { + type IvSize = U16; +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl KeyIvInit for Aes192Ctr { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut ctr = CTR::new().expect("wc_AesInit failed"); + ctr.init(key.as_ref(), iv.as_ref()).expect("wc_AesSetKeyDirect failed"); + Self { inner: ctr } + } +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl StreamCipher for Aes192Ctr { + fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> { + Ok(()) + } + + fn unchecked_apply_keystream_inout(&mut self, mut buf: cipher::InOutBuf<'_, '_, u8>) { + let len = buf.len(); + if len == 0 { return; } + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + // SAFETY: CTR in-place is valid; raw ptrs used to avoid aliasing rules. + let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; + let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; + self.inner.encrypt(in_slice, out_slice).expect("wc_AesCtrEncrypt failed"); + } + + fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { + buf.fill(0); + self.unchecked_apply_keystream_inout(buf.into()); + } +} + +/// AES-256 CTR stream cipher implementing [`cipher::StreamCipher`]. +#[cfg(all(aes_ctr, feature = "cipher"))] +pub struct Aes256Ctr { + inner: CTR, +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl cipher::KeySizeUser for Aes256Ctr { + type KeySize = U32; +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl IvSizeUser for Aes256Ctr { + type IvSize = U16; +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl KeyIvInit for Aes256Ctr { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut ctr = CTR::new().expect("wc_AesInit failed"); + ctr.init(key.as_ref(), iv.as_ref()).expect("wc_AesSetKeyDirect failed"); + Self { inner: ctr } + } +} + +#[cfg(all(aes_ctr, feature = "cipher"))] +impl StreamCipher for Aes256Ctr { + fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> { + Ok(()) + } + + fn unchecked_apply_keystream_inout(&mut self, mut buf: cipher::InOutBuf<'_, '_, u8>) { + let len = buf.len(); + if len == 0 { return; } + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + // SAFETY: CTR in-place is valid; raw ptrs used to avoid aliasing rules. + let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; + let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; + self.inner.encrypt(in_slice, out_slice).expect("wc_AesCtrEncrypt failed"); + } + + fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { + buf.fill(0); + self.unchecked_apply_keystream_inout(buf.into()); + } +} + +// --------------------------------------------------------------------------- +// AES-OFB cipher trait implementations +// --------------------------------------------------------------------------- + +/// AES-128 OFB stream cipher implementing [`cipher::StreamCipher`]. +/// +/// OFB (Output FeedBack) generates a keystream independent of the data, so +/// [`StreamCipher::apply_keystream`] is self-inverse and wolfCrypt supports +/// in-place operation. +#[cfg(all(aes_ofb, feature = "cipher"))] +pub struct Aes128Ofb { + inner: OFB, +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes128Ofb { + type KeySize = U16; +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl IvSizeUser for Aes128Ofb { + type IvSize = U16; +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl KeyIvInit for Aes128Ofb { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut ofb = OFB::new().expect("wc_AesInit failed"); + ofb.init(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ofb } + } +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl StreamCipher for Aes128Ofb { + fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> { + Ok(()) + } + + fn unchecked_apply_keystream_inout(&mut self, mut buf: cipher::InOutBuf<'_, '_, u8>) { + let len = buf.len(); + if len == 0 { return; } + // wolfCrypt AES-OFB supports in-place operation (out == in). + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + // SAFETY: OFB in-place is valid; raw ptrs used to avoid aliasing rules. + let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; + let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; + self.inner.encrypt(in_slice, out_slice).expect("wc_AesOfbEncrypt failed"); + } + + fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { + buf.fill(0); + self.unchecked_apply_keystream_inout(buf.into()); + } +} + +/// AES-192 OFB stream cipher implementing [`cipher::StreamCipher`]. +#[cfg(all(aes_ofb, feature = "cipher"))] +pub struct Aes192Ofb { + inner: OFB, +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes192Ofb { + type KeySize = U24; +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl IvSizeUser for Aes192Ofb { + type IvSize = U16; +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl KeyIvInit for Aes192Ofb { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut ofb = OFB::new().expect("wc_AesInit failed"); + ofb.init(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ofb } + } +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl StreamCipher for Aes192Ofb { + fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> { + Ok(()) + } + + fn unchecked_apply_keystream_inout(&mut self, mut buf: cipher::InOutBuf<'_, '_, u8>) { + let len = buf.len(); + if len == 0 { return; } + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + // SAFETY: OFB in-place is valid; raw ptrs used to avoid aliasing rules. + let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; + let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; + self.inner.encrypt(in_slice, out_slice).expect("wc_AesOfbEncrypt failed"); + } + + fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { + buf.fill(0); + self.unchecked_apply_keystream_inout(buf.into()); + } +} + +/// AES-256 OFB stream cipher implementing [`cipher::StreamCipher`]. +#[cfg(all(aes_ofb, feature = "cipher"))] +pub struct Aes256Ofb { + inner: OFB, +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl cipher::KeySizeUser for Aes256Ofb { + type KeySize = U32; +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl IvSizeUser for Aes256Ofb { + type IvSize = U16; +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl KeyIvInit for Aes256Ofb { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut ofb = OFB::new().expect("wc_AesInit failed"); + ofb.init(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: ofb } + } +} + +#[cfg(all(aes_ofb, feature = "cipher"))] +impl StreamCipher for Aes256Ofb { + fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> { + Ok(()) + } + + fn unchecked_apply_keystream_inout(&mut self, mut buf: cipher::InOutBuf<'_, '_, u8>) { + let len = buf.len(); + if len == 0 { return; } + let in_ptr = buf.get_in().as_ptr(); + let out_ptr = buf.get_out().as_mut_ptr(); + // SAFETY: OFB in-place is valid; raw ptrs used to avoid aliasing rules. + let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; + let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; + self.inner.encrypt(in_slice, out_slice).expect("wc_AesOfbEncrypt failed"); + } + + fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { + buf.fill(0); + self.unchecked_apply_keystream_inout(buf.into()); + } +} + +// --------------------------------------------------------------------------- +// AES-CBC block mode trait implementations +// --------------------------------------------------------------------------- + +/// AES-128 CBC block cipher (encryption) implementing [`cipher::BlockModeEncrypt`]. +/// +/// CBC chains ciphertext blocks: each plaintext block is XORed with the +/// previous ciphertext block (or the IV for the first block) before +/// encryption. wolfCrypt maintains the IV state internally between calls, +/// so blocks can be encrypted one at a time and the chaining is preserved. +#[cfg(all(aes_cbc, feature = "cipher"))] +pub struct Aes128CbcEnc { + inner: CBC, +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::KeySizeUser for Aes128CbcEnc { + type KeySize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128CbcEnc { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl IvSizeUser for Aes128CbcEnc { + type IvSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl KeyIvInit for Aes128CbcEnc { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut cbc = CBC::new().expect("wc_AesInit failed"); + cbc.init_encrypt(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: cbc } + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +struct Aes128CbcEncBackend<'a>(&'a mut Aes128CbcEnc); + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128CbcEncBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl ParBlocksSizeUser for Aes128CbcEncBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeEncBackend for Aes128CbcEncBackend<'_> { + fn encrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.encrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesCbcEncrypt failed"); + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeEncrypt for Aes128CbcEnc { + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure) { + f.call(&mut Aes128CbcEncBackend(self)); + } +} + +/// AES-192 CBC block cipher (encryption) implementing [`cipher::BlockModeEncrypt`]. +#[cfg(all(aes_cbc, feature = "cipher"))] +pub struct Aes192CbcEnc { + inner: CBC, +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::KeySizeUser for Aes192CbcEnc { + type KeySize = U24; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192CbcEnc { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl IvSizeUser for Aes192CbcEnc { + type IvSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl KeyIvInit for Aes192CbcEnc { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut cbc = CBC::new().expect("wc_AesInit failed"); + cbc.init_encrypt(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: cbc } + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +struct Aes192CbcEncBackend<'a>(&'a mut Aes192CbcEnc); + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192CbcEncBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl ParBlocksSizeUser for Aes192CbcEncBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeEncBackend for Aes192CbcEncBackend<'_> { + fn encrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.encrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesCbcEncrypt failed"); + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeEncrypt for Aes192CbcEnc { + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure) { + f.call(&mut Aes192CbcEncBackend(self)); + } +} + +/// AES-256 CBC block cipher (encryption) implementing [`cipher::BlockModeEncrypt`]. +#[cfg(all(aes_cbc, feature = "cipher"))] +pub struct Aes256CbcEnc { + inner: CBC, +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::KeySizeUser for Aes256CbcEnc { + type KeySize = U32; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256CbcEnc { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl IvSizeUser for Aes256CbcEnc { + type IvSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl KeyIvInit for Aes256CbcEnc { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut cbc = CBC::new().expect("wc_AesInit failed"); + cbc.init_encrypt(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: cbc } + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +struct Aes256CbcEncBackend<'a>(&'a mut Aes256CbcEnc); + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256CbcEncBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl ParBlocksSizeUser for Aes256CbcEncBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeEncBackend for Aes256CbcEncBackend<'_> { + fn encrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.encrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesCbcEncrypt failed"); + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeEncrypt for Aes256CbcEnc { + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure) { + f.call(&mut Aes256CbcEncBackend(self)); + } +} + +/// AES-128 CBC block cipher (decryption) implementing [`cipher::BlockModeDecrypt`]. +/// +/// wolfCrypt maintains the IV state (last ciphertext block) internally, so +/// blocks can be decrypted one at a time and the chaining is preserved. +#[cfg(all(aes_cbc, feature = "cipher"))] +pub struct Aes128CbcDec { + inner: CBC, +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::KeySizeUser for Aes128CbcDec { + type KeySize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128CbcDec { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl IvSizeUser for Aes128CbcDec { + type IvSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl KeyIvInit for Aes128CbcDec { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut cbc = CBC::new().expect("wc_AesInit failed"); + cbc.init_decrypt(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: cbc } + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +struct Aes128CbcDecBackend<'a>(&'a mut Aes128CbcDec); + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes128CbcDecBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl ParBlocksSizeUser for Aes128CbcDecBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeDecBackend for Aes128CbcDecBackend<'_> { + fn decrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.decrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesCbcDecrypt failed"); + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeDecrypt for Aes128CbcDec { + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure) { + f.call(&mut Aes128CbcDecBackend(self)); + } +} + +/// AES-192 CBC block cipher (decryption) implementing [`cipher::BlockModeDecrypt`]. +#[cfg(all(aes_cbc, feature = "cipher"))] +pub struct Aes192CbcDec { + inner: CBC, +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::KeySizeUser for Aes192CbcDec { + type KeySize = U24; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192CbcDec { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl IvSizeUser for Aes192CbcDec { + type IvSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl KeyIvInit for Aes192CbcDec { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut cbc = CBC::new().expect("wc_AesInit failed"); + cbc.init_decrypt(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: cbc } + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +struct Aes192CbcDecBackend<'a>(&'a mut Aes192CbcDec); + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes192CbcDecBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl ParBlocksSizeUser for Aes192CbcDecBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeDecBackend for Aes192CbcDecBackend<'_> { + fn decrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.decrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesCbcDecrypt failed"); + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeDecrypt for Aes192CbcDec { + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure) { + f.call(&mut Aes192CbcDecBackend(self)); + } +} + +/// AES-256 CBC block cipher (decryption) implementing [`cipher::BlockModeDecrypt`]. +#[cfg(all(aes_cbc, feature = "cipher"))] +pub struct Aes256CbcDec { + inner: CBC, +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::KeySizeUser for Aes256CbcDec { + type KeySize = U32; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256CbcDec { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl IvSizeUser for Aes256CbcDec { + type IvSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl KeyIvInit for Aes256CbcDec { + fn new(key: &cipher::Key, iv: &cipher::Iv) -> Self { + let mut cbc = CBC::new().expect("wc_AesInit failed"); + cbc.init_decrypt(key.as_ref(), iv.as_ref()).expect("wc_AesSetKey failed"); + Self { inner: cbc } + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +struct Aes256CbcDecBackend<'a>(&'a mut Aes256CbcDec); + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl cipher::BlockSizeUser for Aes256CbcDecBackend<'_> { + type BlockSize = U16; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl ParBlocksSizeUser for Aes256CbcDecBackend<'_> { + type ParBlocksSize = cipher::typenum::consts::U1; +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeDecBackend for Aes256CbcDecBackend<'_> { + fn decrypt_block(&mut self, mut block: cipher::InOut<'_, '_, cipher::Block>) { + let in_block = *block.get_in(); + let out = block.get_out(); + self.0.inner.decrypt(in_block.as_ref(), out.as_mut()).expect("wc_AesCbcDecrypt failed"); + } +} + +#[cfg(all(aes_cbc, feature = "cipher"))] +impl BlockModeDecrypt for Aes256CbcDec { + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure) { + f.call(&mut Aes256CbcDecBackend(self)); + } +} + fn new_ws_aes(heap: Option<*mut core::ffi::c_void>, dev_id: Option) -> Result { let heap = match heap { Some(heap) => heap, diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs index cc916fabde6..e215891691b 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs @@ -1114,3 +1114,312 @@ fn test_aes256ccm_aead_roundtrip() { assert_eq!(recovered, plaintext); } + +// --------------------------------------------------------------------------- +// AES cipher crate trait tests +// --------------------------------------------------------------------------- + +/// Test AES-128-ECB encryption against the known test vector used in the +/// existing `test_ecb_encrypt_decrypt` test. +#[test] +#[cfg(all(feature = "cipher", aes_ecb))] +fn test_aes128_ecb_enc_block_encrypt() { + use cipher::{BlockModeEncrypt, KeyInit}; + use wolfssl_wolfcrypt::aes::Aes128EcbEnc; + + let key: [u8; 16] = *b"0123456789abcdef"; + let plaintext: [u8; 16] = [ + 0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + ]; + let expected: [u8; 16] = [ + 0xd0, 0xc9, 0xd9, 0xc9, 0x40, 0xe8, 0x97, 0xb6, + 0xc8, 0x8c, 0x33, 0x3b, 0xb5, 0x8f, 0x85, 0xd1, + ]; + + let mut enc = Aes128EcbEnc::new_from_slice(&key).expect("key init failed"); + let mut block = cipher::Block::::try_from(&plaintext[..]).unwrap(); + enc.encrypt_block(&mut block); + assert_eq!(block.as_slice(), &expected); +} + +/// Test AES-128-ECB decryption matches the plaintext after encryption. +#[test] +#[cfg(all(feature = "cipher", aes_ecb))] +fn test_aes128_ecb_dec_block_decrypt() { + use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyInit}; + use wolfssl_wolfcrypt::aes::{Aes128EcbDec, Aes128EcbEnc}; + + let key: [u8; 16] = *b"0123456789abcdef"; + let plaintext: [u8; 16] = [ + 0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + ]; + + let mut enc = Aes128EcbEnc::new_from_slice(&key).expect("enc init failed"); + let mut dec = Aes128EcbDec::new_from_slice(&key).expect("dec init failed"); + + let mut block = cipher::Block::::try_from(&plaintext[..]).unwrap(); + enc.encrypt_block(&mut block); + + let mut block2 = cipher::Block::::try_from(block.as_slice()).unwrap(); + dec.decrypt_block(&mut block2); + + assert_eq!(block2.as_slice(), &plaintext); +} + +/// Test AES-256-ECB encryption and decryption roundtrip. +#[test] +#[cfg(all(feature = "cipher", aes_ecb))] +fn test_aes256_ecb_roundtrip() { + use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyInit}; + use wolfssl_wolfcrypt::aes::{Aes256EcbDec, Aes256EcbEnc}; + + let key = [0xabu8; 32]; + let plaintext = [0x5cu8; 16]; + + let mut enc = Aes256EcbEnc::new_from_slice(&key).expect("enc init failed"); + let mut dec = Aes256EcbDec::new_from_slice(&key).expect("dec init failed"); + + let mut block = cipher::Block::::try_from(&plaintext[..]).unwrap(); + enc.encrypt_block(&mut block); + assert_ne!(block.as_slice(), &plaintext, "encrypted block should differ from plaintext"); + + let mut block2 = cipher::Block::::try_from(block.as_slice()).unwrap(); + dec.decrypt_block(&mut block2); + assert_eq!(block2.as_slice(), &plaintext); +} + +/// Test AES-128-CTR `apply_keystream` against the NIST CTR test vector. +#[test] +#[cfg(all(feature = "cipher", aes_ctr))] +fn test_aes128_ctr_apply_keystream() { + use cipher::{KeyIvInit, StreamCipher}; + use wolfssl_wolfcrypt::aes::Aes128Ctr; + + let key: [u8; 16] = [ + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + ]; + let iv: [u8; 16] = [ + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + ]; + let plaintext: [u8; 64] = [ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, + ]; + let expected_ciphertext: [u8; 64] = [ + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff, + 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, + 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab, + 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, + 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee, + ]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut enc = Aes128Ctr::new(&key_arr, &iv_arr); + let mut data = plaintext; + enc.apply_keystream(&mut data); + assert_eq!(data, expected_ciphertext); + + // apply_keystream is self-inverse: applying again must recover plaintext. + let mut dec = Aes128Ctr::new(&key_arr, &iv_arr); + dec.apply_keystream(&mut data); + assert_eq!(data, plaintext); +} + +/// Test AES-256-CTR roundtrip via `apply_keystream`. +#[test] +#[cfg(all(feature = "cipher", aes_ctr))] +fn test_aes256_ctr_roundtrip() { + use cipher::{KeyIvInit, StreamCipher}; + use wolfssl_wolfcrypt::aes::Aes256Ctr; + + let key = [0x01u8; 32]; + let iv = [0x02u8; 16]; + let plaintext = [0x55u8; 48]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + + let mut enc = Aes256Ctr::new(&key_arr, &iv_arr); + let mut data = plaintext; + enc.apply_keystream(&mut data); + assert_ne!(data, plaintext); + + let mut dec = Aes256Ctr::new(&key_arr, &iv_arr); + dec.apply_keystream(&mut data); + assert_eq!(data, plaintext); +} + +/// Test AES-256-OFB `apply_keystream` against the known OFB test vector. +#[test] +#[cfg(all(feature = "cipher", aes_ofb))] +fn test_aes256_ofb_apply_keystream() { + use cipher::{KeyIvInit, StreamCipher}; + use wolfssl_wolfcrypt::aes::Aes256Ofb; + + let key: [u8; 32] = [ + 0xc4, 0xc7, 0xfa, 0xd6, 0x53, 0x5c, 0xb8, 0x71, + 0x4a, 0x5c, 0x40, 0x77, 0x9a, 0x8b, 0xa1, 0xd2, + 0x53, 0x3e, 0x23, 0xb4, 0xb2, 0x58, 0x73, 0x2a, + 0x5b, 0x78, 0x01, 0xf4, 0xe3, 0x71, 0xa7, 0x94, + ]; + let iv: [u8; 16] = [ + 0x5e, 0xb9, 0x33, 0x13, 0xb8, 0x71, 0xff, 0x16, + 0xb9, 0x8a, 0x9b, 0xcb, 0x43, 0x33, 0x0d, 0x6f, + ]; + let plaintext: [u8; 48] = [ + 0x6d, 0x0b, 0xb0, 0x79, 0x63, 0x84, 0x71, 0xe9, + 0x39, 0xd4, 0x53, 0x14, 0x86, 0xc1, 0x4c, 0x25, + 0x9a, 0xee, 0xc6, 0xf3, 0xc0, 0x0d, 0xfd, 0xd6, + 0xc0, 0x50, 0xa8, 0xba, 0xa8, 0x20, 0xdb, 0x71, + 0xcc, 0x12, 0x2c, 0x4e, 0x0c, 0x17, 0x15, 0xef, + 0x55, 0xf3, 0x99, 0x5a, 0x6b, 0xf0, 0x2a, 0x4c, + ]; + let expected_ciphertext: [u8; 48] = [ + 0x0f, 0x54, 0x61, 0x71, 0x59, 0xd0, 0x3f, 0xfc, + 0x1b, 0xfa, 0xfb, 0x60, 0x29, 0x30, 0xd7, 0x00, + 0xf4, 0xa4, 0xa8, 0xe6, 0xdd, 0x93, 0x94, 0x46, + 0x64, 0xd2, 0x19, 0xc4, 0xc5, 0x4d, 0xde, 0x1b, + 0x04, 0x53, 0xe1, 0x73, 0xf5, 0x18, 0x74, 0xae, + 0xfd, 0x64, 0xa2, 0xe1, 0xe2, 0x76, 0x13, 0xb0, + ]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut enc = Aes256Ofb::new(&key_arr, &iv_arr); + let mut data = plaintext; + enc.apply_keystream(&mut data); + assert_eq!(data, expected_ciphertext); + + // apply_keystream is self-inverse for OFB (same keystream for enc/dec). + let mut dec = Aes256Ofb::new(&key_arr, &iv_arr); + dec.apply_keystream(&mut data); + assert_eq!(data, plaintext); +} + +/// Test AES-128-OFB roundtrip via `apply_keystream`. +#[test] +#[cfg(all(feature = "cipher", aes_ofb))] +fn test_aes128_ofb_roundtrip() { + use cipher::{KeyIvInit, StreamCipher}; + use wolfssl_wolfcrypt::aes::Aes128Ofb; + + let key = [0xddu8; 16]; + let iv = [0xeeu8; 16]; + let plaintext = [0x42u8; 32]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + + let mut enc = Aes128Ofb::new(&key_arr, &iv_arr); + let mut data = plaintext; + enc.apply_keystream(&mut data); + assert_ne!(data, plaintext); + + let mut dec = Aes128Ofb::new(&key_arr, &iv_arr); + dec.apply_keystream(&mut data); + assert_eq!(data, plaintext); +} + +/// Test AES-128-CBC encryption against a known vector (same as test_cbc_encrypt_decrypt). +#[test] +#[cfg(all(feature = "cipher", aes_cbc))] +fn test_aes128_cbc_enc_block_mode() { + use cipher::{BlockModeEncrypt, KeyIvInit}; + use wolfssl_wolfcrypt::aes::Aes128CbcEnc; + + let key: [u8; 16] = *b"0123456789abcdef"; + let iv: [u8; 16] = *b"1234567890abcdef"; + let plaintext: [u8; 16] = [ + 0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + ]; + let expected: [u8; 16] = [ + 0x95, 0x94, 0x92, 0x57, 0x5f, 0x42, 0x81, 0x53, + 0x2c, 0xcc, 0x9d, 0x46, 0x77, 0xa2, 0x33, 0xcb, + ]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut enc = Aes128CbcEnc::new(&key_arr, &iv_arr); + let mut block = cipher::Block::::try_from(&plaintext[..]).unwrap(); + enc.encrypt_block(&mut block); + assert_eq!(block.as_slice(), &expected); +} + +/// Test AES-128-CBC decryption roundtrip. +#[test] +#[cfg(all(feature = "cipher", aes_cbc))] +fn test_aes128_cbc_dec_block_mode() { + use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit}; + use wolfssl_wolfcrypt::aes::{Aes128CbcDec, Aes128CbcEnc}; + + let key: [u8; 16] = *b"0123456789abcdef"; + let iv: [u8; 16] = *b"1234567890abcdef"; + let plaintext: [u8; 16] = [ + 0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + ]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut enc = Aes128CbcEnc::new(&key_arr, &iv_arr); + let mut block = cipher::Block::::try_from(&plaintext[..]).unwrap(); + enc.encrypt_block(&mut block); + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut dec = Aes128CbcDec::new(&key_arr, &iv_arr); + dec.decrypt_block(&mut block); + assert_eq!(block.as_slice(), &plaintext); +} + +/// Test AES-256-CBC encryption/decryption roundtrip across multiple blocks. +#[test] +#[cfg(all(feature = "cipher", aes_cbc))] +fn test_aes256_cbc_roundtrip() { + use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit}; + use wolfssl_wolfcrypt::aes::{Aes256CbcDec, Aes256CbcEnc}; + + let key = [0xabu8; 32]; + let iv = [0xcdu8; 16]; + let plaintext = [[0x5cu8; 16], [0x3au8; 16], [0x1eu8; 16]]; + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut enc = Aes256CbcEnc::new(&key_arr, &iv_arr); + let mut blocks: [cipher::Block; 3] = plaintext + .iter() + .map(|b| cipher::Block::::try_from(b.as_ref()).unwrap()) + .collect::>() + .try_into() + .unwrap(); + for block in blocks.iter_mut() { + enc.encrypt_block(block); + } + // Ciphertext must differ from plaintext due to key and IV mixing. + assert!(blocks.iter().zip(plaintext.iter()).any(|(c, p)| c.as_slice() != p)); + + let key_arr = cipher::Key::::try_from(&key[..]).unwrap(); + let iv_arr = cipher::Iv::::try_from(&iv[..]).unwrap(); + let mut dec = Aes256CbcDec::new(&key_arr, &iv_arr); + for block in blocks.iter_mut() { + dec.decrypt_block(block); + } + for (block, expected) in blocks.iter().zip(plaintext.iter()) { + assert_eq!(block.as_slice(), expected); + } +} From 5d9439c5818bf590c0dca3970d9ef3773d30585c Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Wed, 25 Mar 2026 13:43:19 -0400 Subject: [PATCH 4/6] Rust wrapper: aes: avoid overlapping Rust slices --- wrapper/rust/wolfssl-wolfcrypt/src/aes.rs | 48 +++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs b/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs index a85d1bab0b9..7b58b25f260 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/aes.rs @@ -3257,10 +3257,10 @@ impl StreamCipher for Aes128Ctr { // wolfCrypt AES-CTR supports in-place operation (out == in). let in_ptr = buf.get_in().as_ptr(); let out_ptr = buf.get_out().as_mut_ptr(); - // SAFETY: CTR in-place is valid; raw ptrs used to avoid aliasing rules. - let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; - let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; - self.inner.encrypt(in_slice, out_slice).expect("wc_AesCtrEncrypt failed"); + // SAFETY: CTR in-place is valid; C function called directly to avoid + // creating aliasing slices. + let rc = unsafe { sys::wc_AesCtrEncrypt(&mut self.inner.ws_aes, out_ptr, in_ptr, len as u32) }; + assert_eq!(rc, 0, "wc_AesCtrEncrypt failed"); } fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { @@ -3305,10 +3305,10 @@ impl StreamCipher for Aes192Ctr { if len == 0 { return; } let in_ptr = buf.get_in().as_ptr(); let out_ptr = buf.get_out().as_mut_ptr(); - // SAFETY: CTR in-place is valid; raw ptrs used to avoid aliasing rules. - let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; - let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; - self.inner.encrypt(in_slice, out_slice).expect("wc_AesCtrEncrypt failed"); + // SAFETY: CTR in-place is valid; C function called directly to avoid + // creating aliasing slices. + let rc = unsafe { sys::wc_AesCtrEncrypt(&mut self.inner.ws_aes, out_ptr, in_ptr, len as u32) }; + assert_eq!(rc, 0, "wc_AesCtrEncrypt failed"); } fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { @@ -3353,10 +3353,10 @@ impl StreamCipher for Aes256Ctr { if len == 0 { return; } let in_ptr = buf.get_in().as_ptr(); let out_ptr = buf.get_out().as_mut_ptr(); - // SAFETY: CTR in-place is valid; raw ptrs used to avoid aliasing rules. - let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; - let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; - self.inner.encrypt(in_slice, out_slice).expect("wc_AesCtrEncrypt failed"); + // SAFETY: CTR in-place is valid; C function called directly to avoid + // creating aliasing slices. + let rc = unsafe { sys::wc_AesCtrEncrypt(&mut self.inner.ws_aes, out_ptr, in_ptr, len as u32) }; + assert_eq!(rc, 0, "wc_AesCtrEncrypt failed"); } fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { @@ -3410,10 +3410,10 @@ impl StreamCipher for Aes128Ofb { // wolfCrypt AES-OFB supports in-place operation (out == in). let in_ptr = buf.get_in().as_ptr(); let out_ptr = buf.get_out().as_mut_ptr(); - // SAFETY: OFB in-place is valid; raw ptrs used to avoid aliasing rules. - let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; - let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; - self.inner.encrypt(in_slice, out_slice).expect("wc_AesOfbEncrypt failed"); + // SAFETY: OFB in-place is valid; C function called directly to avoid + // creating aliasing slices. + let rc = unsafe { sys::wc_AesOfbEncrypt(&mut self.inner.ws_aes, out_ptr, in_ptr, len as u32) }; + assert_eq!(rc, 0, "wc_AesOfbEncrypt failed"); } fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { @@ -3458,10 +3458,10 @@ impl StreamCipher for Aes192Ofb { if len == 0 { return; } let in_ptr = buf.get_in().as_ptr(); let out_ptr = buf.get_out().as_mut_ptr(); - // SAFETY: OFB in-place is valid; raw ptrs used to avoid aliasing rules. - let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; - let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; - self.inner.encrypt(in_slice, out_slice).expect("wc_AesOfbEncrypt failed"); + // SAFETY: OFB in-place is valid; C function called directly to avoid + // creating aliasing slices. + let rc = unsafe { sys::wc_AesOfbEncrypt(&mut self.inner.ws_aes, out_ptr, in_ptr, len as u32) }; + assert_eq!(rc, 0, "wc_AesOfbEncrypt failed"); } fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { @@ -3506,10 +3506,10 @@ impl StreamCipher for Aes256Ofb { if len == 0 { return; } let in_ptr = buf.get_in().as_ptr(); let out_ptr = buf.get_out().as_mut_ptr(); - // SAFETY: OFB in-place is valid; raw ptrs used to avoid aliasing rules. - let in_slice = unsafe { core::slice::from_raw_parts(in_ptr, len) }; - let out_slice = unsafe { core::slice::from_raw_parts_mut(out_ptr, len) }; - self.inner.encrypt(in_slice, out_slice).expect("wc_AesOfbEncrypt failed"); + // SAFETY: OFB in-place is valid; C function called directly to avoid + // creating aliasing slices. + let rc = unsafe { sys::wc_AesOfbEncrypt(&mut self.inner.ws_aes, out_ptr, in_ptr, len as u32) }; + assert_eq!(rc, 0, "wc_AesOfbEncrypt failed"); } fn unchecked_write_keystream(&mut self, buf: &mut [u8]) { From 444f90553abb2efc0698c78cb9d863c4ab99546b Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Wed, 25 Mar 2026 13:48:03 -0400 Subject: [PATCH 5/6] Rust wrapper: document XChaCha20Poly1305 encrypt_in_place_detached maximum buffer length --- wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs b/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs index f3c3ec67432..bce16383e92 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs @@ -459,6 +459,7 @@ impl aead::KeyInit for XChaCha20Poly1305Aead { #[cfg(all(xchacha20_poly1305, feature = "aead"))] impl aead::AeadInPlace for XChaCha20Poly1305Aead { + // This function can encrypt a maximum of 4096 bytes. fn encrypt_in_place_detached( &self, nonce: &aead::Nonce, From aa33d7be356c4f0a4387a649e37ed496a292bfe9 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Thu, 26 Mar 2026 09:08:39 -0400 Subject: [PATCH 6/6] Rust wrapper: chacha20_poly1305: add debug_assert checking in-place operation maximum length --- wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs b/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs index bce16383e92..a7002eef0e0 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/chacha20_poly1305.rs @@ -470,6 +470,7 @@ impl aead::AeadInPlace for XChaCha20Poly1305Aead { // single output buffer. Use a stack buffer to hold both, then split // the tag out and copy the ciphertext back over the caller's buffer. const MAX_INLINE: usize = 4096; + debug_assert!(buffer.len() <= MAX_INLINE, "Maximum of 4096 bytes supported"); if buffer.len() > MAX_INLINE { return Err(aead::Error); } @@ -495,6 +496,7 @@ impl aead::AeadInPlace for XChaCha20Poly1305Aead { Ok(tag) } + // This function can decrypt a maximum of 4096 bytes. fn decrypt_in_place_detached( &self, nonce: &aead::Nonce, @@ -505,6 +507,7 @@ impl aead::AeadInPlace for XChaCha20Poly1305Aead { // wc_XChaCha20Poly1305_Decrypt expects the auth tag appended after the // ciphertext. Build a combined [ciphertext | tag] buffer on the stack. const MAX_INLINE: usize = 4096; + debug_assert!(buffer.len() <= MAX_INLINE, "Maximum of 4096 bytes supported"); if buffer.len() > MAX_INLINE { return Err(aead::Error); }