diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index cab8c54884..3a6dbadd24 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 08cd8e1843..7dca3510e0 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 e1db9ee9e7..37dc9a8579 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 0cad0c7f73..a85d1bab0b 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 cc916fabde..e215891691 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); + } +}