crypto: Implement Aes

Implement AES and AES-GCM using the GP API, and common block cipher
operations including PKCS#7 padding in operation.rs.

Note that OP-TEE needs the following change to pass KeyMint VTS block
cipher tests:

https://github.com/OP-TEE/optee_os/commit/aeb530a5a74acddd0badc78af47fce57db8c4644

Test: make ta + unit tests
Change-Id: I4fc94ebff0f3e17238aba546fee657886efbba10
diff --git a/src/crypto.rs b/src/crypto.rs
index 9288733..405dbc3 100644
--- a/src/crypto.rs
+++ b/src/crypto.rs
@@ -18,9 +18,11 @@
 use kmr_common::crypto::RawKeyMaterial;
 use kmr_common::vec_try;
 
+pub mod aes;
 pub mod clock;
 pub mod eq;
 pub mod mac;
+pub mod operation;
 pub mod rng;
 pub mod sha;
 
diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs
new file mode 100644
index 0000000..7fad9ba
--- /dev/null
+++ b/src/crypto/aes.rs
@@ -0,0 +1,183 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::crypto::operation;
+use crate::crypto::operation::{
+    BlockCipher, BlockOperation, BufferedOperation, Pkcs7DecryptOperation, Pkcs7EncryptOperation,
+};
+use crate::error::Error;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use kmr_common::{crypto, km_err, vec_try};
+use optee_utee::{
+    AlgorithmId, AttributeId, AttributeMemref, Cipher, OperationMode, TransientObject,
+    TransientObjectType, AE,
+};
+
+pub struct Aes;
+
+impl crypto::Aes for Aes {
+    fn begin(
+        &self,
+        key: crypto::OpaqueOr<crypto::aes::Key>,
+        mode: crypto::aes::CipherMode,
+        dir: crypto::SymmetricOperation,
+    ) -> Result<Box<dyn crypto::EmittingOperation>, kmr_common::Error> {
+        let operation = match &dir {
+            crypto::SymmetricOperation::Encrypt => OperationMode::Encrypt,
+            crypto::SymmetricOperation::Decrypt => OperationMode::Decrypt,
+        };
+
+        let (algorithm, nonce) = match &mode {
+            crypto::aes::CipherMode::EcbNoPadding | crypto::aes::CipherMode::EcbPkcs7Padding => {
+                (AlgorithmId::AesEcbNopad, &[0u8; 0][..])
+            }
+            crypto::aes::CipherMode::CbcNoPadding { nonce: n }
+            | crypto::aes::CipherMode::CbcPkcs7Padding { nonce: n } => {
+                (AlgorithmId::AesCbcNopad, &n[..])
+            }
+            crypto::aes::CipherMode::Ctr { nonce: n } => (AlgorithmId::AesCtr, &n[..]),
+        };
+
+        let key = kmr_common::explicit!(key)?;
+        let keybuf = match &key {
+            crypto::aes::Key::Aes128(k) => &k[..],
+            crypto::aes::Key::Aes192(k) => &k[..],
+            crypto::aes::Key::Aes256(k) => &k[..],
+        };
+
+        let mut keyobj = TransientObject::allocate(TransientObjectType::Aes, key.size().0 as usize)
+            .map_err(Error::kmerr)?;
+        let attr = AttributeMemref::from_ref(AttributeId::SecretValue, keybuf);
+        keyobj.populate(&[attr.into()]).map_err(Error::kmerr)?;
+
+        let cipher =
+            Cipher::allocate(algorithm, operation, key.size().0 as usize).map_err(Error::kmerr)?;
+        cipher.set_key(&keyobj).map_err(Error::kmerr)?;
+        cipher.init(nonce);
+
+        match mode {
+            crypto::aes::CipherMode::EcbPkcs7Padding
+            | crypto::aes::CipherMode::CbcPkcs7Padding { nonce: _ } => match dir {
+                crypto::SymmetricOperation::Encrypt => {
+                    Ok(Box::new(Pkcs7EncryptOperation::new(crypto::aes::BLOCK_SIZE, cipher)))
+                }
+                crypto::SymmetricOperation::Decrypt => {
+                    Ok(Box::new(Pkcs7DecryptOperation::new(crypto::aes::BLOCK_SIZE, cipher)))
+                }
+            },
+            crypto::aes::CipherMode::Ctr { nonce: _ } => Ok(Box::new(BlockOperation::new(
+                crypto::aes::BLOCK_SIZE,
+                BlockCipher::Counter(cipher),
+            ))),
+            _ => Ok(Box::new(BlockOperation::new(
+                crypto::aes::BLOCK_SIZE,
+                BlockCipher::Block(cipher),
+            ))),
+        }
+    }
+
+    fn begin_aead(
+        &self,
+        key: crypto::OpaqueOr<crypto::aes::Key>,
+        mode: crypto::aes::GcmMode,
+        dir: crypto::SymmetricOperation,
+    ) -> Result<Box<dyn crypto::AadOperation>, kmr_common::Error> {
+        let operation = match &dir {
+            crypto::SymmetricOperation::Encrypt => OperationMode::Encrypt,
+            crypto::SymmetricOperation::Decrypt => OperationMode::Decrypt,
+        };
+
+        let (tag_len, nonce) = match &mode {
+            crypto::aes::GcmMode::GcmTag12 { nonce: n }
+            | crypto::aes::GcmMode::GcmTag13 { nonce: n }
+            | crypto::aes::GcmMode::GcmTag14 { nonce: n }
+            | crypto::aes::GcmMode::GcmTag15 { nonce: n }
+            | crypto::aes::GcmMode::GcmTag16 { nonce: n } => (mode.tag_len() * 8, &n[..]),
+        };
+
+        let key = kmr_common::explicit!(key)?;
+        let keybuf = match &key {
+            crypto::aes::Key::Aes128(k) => &k[..],
+            crypto::aes::Key::Aes192(k) => &k[..],
+            crypto::aes::Key::Aes256(k) => &k[..],
+        };
+
+        let mut keyobj = TransientObject::allocate(TransientObjectType::Aes, key.size().0 as usize)
+            .map_err(Error::kmerr)?;
+        let attr = AttributeMemref::from_ref(AttributeId::SecretValue, keybuf);
+        keyobj.populate(&[attr.into()]).map_err(Error::kmerr)?;
+
+        let cipher = AE::allocate(AlgorithmId::AesGcm, operation, key.size().0 as usize)
+            .map_err(Error::kmerr)?;
+        cipher.set_key(&keyobj).map_err(Error::kmerr)?;
+        cipher.init(nonce, tag_len, 0, 0).map_err(Error::kmerr)?;
+
+        let tag_bytes = tag_len / 8;
+        match &dir {
+            crypto::SymmetricOperation::Encrypt => Ok(Box::new(BlockOperation::new(
+                crypto::aes::BLOCK_SIZE,
+                BlockCipher::AEncrypt(operation::AE::new(tag_bytes, cipher)),
+            ))),
+            crypto::SymmetricOperation::Decrypt => {
+                Ok(Box::new(AesGcmDecryptOperation::new(tag_bytes, cipher)))
+            }
+        }
+    }
+}
+
+struct AesGcmDecryptOperation {
+    inner: BufferedOperation,
+}
+
+impl AesGcmDecryptOperation {
+    pub fn new(tag_len: usize, cipher: AE) -> Self {
+        Self {
+            inner: BufferedOperation::new(
+                tag_len,
+                crypto::aes::BLOCK_SIZE,
+                BlockCipher::ADecrypt(operation::AE::new(tag_len, cipher)),
+            ),
+        }
+    }
+}
+
+impl crypto::AadOperation for AesGcmDecryptOperation {
+    fn update_aad(&mut self, aad: &[u8]) -> Result<(), kmr_common::Error> {
+        self.inner.update_aad(aad)
+    }
+}
+
+impl crypto::EmittingOperation for AesGcmDecryptOperation {
+    fn update(&mut self, data: &[u8]) -> Result<Vec<u8>, kmr_common::Error> {
+        self.inner.update(data)
+    }
+
+    fn finish(self: Box<Self>) -> Result<Vec<u8>, kmr_common::Error> {
+        let ae = self.inner.ae()?;
+        if self.inner.buffer.len() != ae.tag_len() {
+            return Err(km_err!(InvalidInputLength, "Input too short (<tag size)"));
+        }
+
+        let mut output = vec_try![0u8; 2 * self.inner.block_size()]?;
+        let len = ae
+            .cipher
+            .decrypt_final(&[], output.as_mut_slice(), self.inner.buffer.as_slice())
+            .map_err(Error::kmerr)?;
+
+        output.truncate(len);
+        Ok(output)
+    }
+}
diff --git a/src/crypto/operation.rs b/src/crypto/operation.rs
new file mode 100644
index 0000000..64e7ecf
--- /dev/null
+++ b/src/crypto/operation.rs
@@ -0,0 +1,305 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::crypto::FfiSlice;
+use crate::error::Error;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use kmr_common::{crypto, km_err, vec_try, FallibleAllocExt};
+
+pub struct AE {
+    tag_len: usize,
+    pub cipher: optee_utee::AE,
+}
+
+impl AE {
+    pub fn new(tag_len: usize, cipher: optee_utee::AE) -> Self {
+        Self { tag_len: tag_len, cipher: cipher }
+    }
+
+    pub fn tag_len(&self) -> usize {
+        self.tag_len
+    }
+}
+
+pub enum BlockCipher {
+    Block(optee_utee::Cipher),
+    Counter(optee_utee::Cipher),
+    AEncrypt(AE),
+    ADecrypt(AE),
+}
+
+pub struct BlockOperation {
+    block_size: usize,
+    input_len: usize,
+    cipher: BlockCipher,
+}
+
+// SAFETY: The raw pointer to TEE_OperationHandle is held only by us, so
+// it can be safely transferred to another thread.
+unsafe impl Send for BlockOperation {}
+
+impl BlockOperation {
+    pub fn new(block_size: usize, cipher: BlockCipher) -> Self {
+        Self { block_size: block_size, input_len: 0, cipher: cipher }
+    }
+
+    pub fn block_size(&self) -> usize {
+        self.block_size
+    }
+
+    pub fn input_len(&self) -> usize {
+        self.input_len
+    }
+
+    pub fn cipher(&self) -> Result<&optee_utee::Cipher, kmr_common::Error> {
+        match &self.cipher {
+            BlockCipher::Block(cipher) | BlockCipher::Counter(cipher) => Ok(cipher),
+            _ => Err(km_err!(Unimplemented, "Expected BlockCipher::Cipher or Counter")),
+        }
+    }
+
+    pub fn ae(&self) -> Result<&AE, kmr_common::Error> {
+        match &self.cipher {
+            BlockCipher::AEncrypt(ae) | BlockCipher::ADecrypt(ae) => Ok(ae),
+            _ => Err(km_err!(Unimplemented, "Expected BlockCipher::A*crypt")),
+        }
+    }
+}
+
+impl crypto::AadOperation for BlockOperation {
+    fn update_aad(&mut self, aad: &[u8]) -> Result<(), kmr_common::Error> {
+        self.ae()?.cipher.update_aad(aad);
+        Ok(())
+    }
+}
+
+impl crypto::EmittingOperation for BlockOperation {
+    fn update(&mut self, data: &[u8]) -> Result<Vec<u8>, kmr_common::Error> {
+        let mut output = vec_try![0u8; data.len() + self.block_size]?;
+        self.input_len += data.len();
+
+        let len = match &self.cipher {
+            BlockCipher::Block(cipher) | BlockCipher::Counter(cipher) => {
+                cipher.update(data.as_ffi_slice(), output.as_mut_slice())
+            }
+            BlockCipher::AEncrypt(ae) | BlockCipher::ADecrypt(ae) => {
+                ae.cipher.update(data.as_ffi_slice(), output.as_mut_slice())
+            }
+        }
+        .map_err(Error::kmerr)?;
+
+        output.truncate(len);
+        Ok(output)
+    }
+
+    fn finish(self: Box<Self>) -> Result<Vec<u8>, kmr_common::Error> {
+        let mut output = vec_try![0u8; 2 * self.block_size]?;
+
+        match self.cipher {
+            BlockCipher::Block(cipher) => {
+                if (self.input_len % self.block_size) != 0 {
+                    return Err(km_err!(
+                        InvalidInputLength,
+                        "Input length not multiple of block size"
+                    ));
+                }
+
+                let len = cipher.do_final(&[], output.as_mut_slice()).map_err(Error::kmerr)?;
+
+                output.truncate(len);
+                Ok(output)
+            }
+            BlockCipher::Counter(cipher) => {
+                let len = cipher.do_final(&[], output.as_mut_slice()).map_err(Error::kmerr)?;
+
+                output.truncate(len);
+                Ok(output)
+            }
+            BlockCipher::AEncrypt(ae) => {
+                let mut tag = vec_try![0u8; ae.tag_len]?;
+
+                let (output_len, tag_len) = ae
+                    .cipher
+                    .encrypt_final(&[], output.as_mut_slice(), tag.as_mut_slice())
+                    .map_err(Error::kmerr)?;
+
+                output.truncate(output_len);
+                output.try_extend_from_slice(tag.as_slice())?;
+
+                if tag_len != ae.tag_len {
+                    Err(km_err!(InvalidTag, "Invalid tag length"))
+                } else {
+                    Ok(output)
+                }
+            }
+            BlockCipher::ADecrypt(_) => {
+                Err(km_err!(Unimplemented, "Owner must implement EmittingOperation::finish"))
+            }
+        }
+    }
+}
+
+pub struct BufferedOperation {
+    inner: BlockOperation,
+    max_size: usize,
+    pub buffer: Vec<u8>,
+}
+
+impl BufferedOperation {
+    pub fn new(max_size: usize, block_size: usize, cipher: BlockCipher) -> Self {
+        Self {
+            inner: BlockOperation::new(block_size, cipher),
+            max_size: max_size,
+            buffer: Vec::new(),
+        }
+    }
+
+    pub fn block_size(&self) -> usize {
+        self.inner.block_size()
+    }
+
+    pub fn input_len(&self) -> usize {
+        self.inner.input_len()
+    }
+
+    pub fn cipher(&self) -> Result<&optee_utee::Cipher, kmr_common::Error> {
+        self.inner.cipher()
+    }
+
+    pub fn ae(&self) -> Result<&AE, kmr_common::Error> {
+        self.inner.ae()
+    }
+}
+
+impl crypto::AadOperation for BufferedOperation {
+    fn update_aad(&mut self, aad: &[u8]) -> Result<(), kmr_common::Error> {
+        self.inner.update_aad(aad)
+    }
+}
+
+impl crypto::EmittingOperation for BufferedOperation {
+    fn update(&mut self, data: &[u8]) -> Result<Vec<u8>, kmr_common::Error> {
+        // Delay outputting the final max_size bytes until finish()
+        self.buffer.try_extend_from_slice(data)?;
+
+        if self.buffer.len() > self.max_size {
+            self.inner.update(
+                self.buffer
+                    .drain(..self.buffer.len() - self.max_size)
+                    .collect::<Vec<u8>>()
+                    .as_slice(),
+            )
+        } else {
+            Ok(Vec::new())
+        }
+    }
+
+    fn finish(self: Box<Self>) -> Result<Vec<u8>, kmr_common::Error> {
+        Err(km_err!(Unimplemented, "Owner must implement EmittingOperation::finish"))
+    }
+}
+
+pub struct Pkcs7EncryptOperation {
+    inner: BlockOperation,
+}
+
+impl Pkcs7EncryptOperation {
+    pub fn new(block_size: usize, cipher: optee_utee::Cipher) -> Self {
+        Self { inner: BlockOperation::new(block_size, BlockCipher::Block(cipher)) }
+    }
+
+    fn get_padding(&self) -> Result<Vec<u8>, kmr_common::Error> {
+        let padding = self.inner.block_size() - (self.inner.input_len() % self.inner.block_size());
+        Ok(vec_try![padding as u8; padding]?)
+    }
+}
+
+impl crypto::EmittingOperation for Pkcs7EncryptOperation {
+    fn update(&mut self, data: &[u8]) -> Result<Vec<u8>, kmr_common::Error> {
+        self.inner.update(data)
+    }
+
+    fn finish(self: Box<Self>) -> Result<Vec<u8>, kmr_common::Error> {
+        let padding = self.get_padding()?;
+        let mut output = vec_try![0u8; 2 * self.inner.block_size()]?;
+
+        let len = self
+            .inner
+            .cipher()?
+            .do_final(padding.as_ffi_slice(), output.as_mut_slice())
+            .map_err(Error::kmerr)?;
+
+        output.truncate(len);
+        Ok(output)
+    }
+}
+
+pub struct Pkcs7DecryptOperation {
+    inner: BufferedOperation,
+}
+
+impl Pkcs7DecryptOperation {
+    pub fn new(block_size: usize, cipher: optee_utee::Cipher) -> Self {
+        Self { inner: BufferedOperation::new(block_size, block_size, BlockCipher::Block(cipher)) }
+    }
+
+    fn validate_padding(&self, data: &mut Vec<u8>) -> Result<usize, kmr_common::Error> {
+        let output_len = data.len();
+        if output_len < 1 {
+            return Err(km_err!(InvalidInputLength, "Input too short for padding"));
+        }
+
+        let padding_len = data[output_len - 1] as usize;
+        if padding_len > self.inner.block_size() {
+            return Err(km_err!(InvalidArgument, "Invalid padding (>block size)"));
+        }
+
+        let unpadded_len = output_len - padding_len;
+        let mut padding: Vec<_> = data.drain(unpadded_len..).collect();
+        padding.retain(|&x| x == padding_len as u8);
+
+        if padding.len() != padding_len {
+            return Err(km_err!(InvalidArgument, "Invalid padding"));
+        }
+
+        Ok(unpadded_len)
+    }
+}
+
+impl crypto::EmittingOperation for Pkcs7DecryptOperation {
+    fn update(&mut self, data: &[u8]) -> Result<Vec<u8>, kmr_common::Error> {
+        self.inner.update(data)
+    }
+
+    fn finish(self: Box<Self>) -> Result<Vec<u8>, kmr_common::Error> {
+        let input_len = self.inner.input_len() + self.inner.buffer.len();
+        if input_len < self.inner.block_size() || (input_len % self.inner.block_size()) != 0 {
+            return Err(km_err!(InvalidInputLength, "Input length not multiple of block size"));
+        }
+
+        let mut output = vec_try![0u8; 2 * self.inner.block_size()]?;
+
+        let len = self
+            .inner
+            .cipher()?
+            .do_final(self.inner.buffer.as_ffi_slice(), output.as_mut_slice())
+            .map_err(Error::kmerr)?;
+
+        output.truncate(len);
+        self.validate_padding(&mut output)?;
+        Ok(output)
+    }
+}
diff --git a/src/crypto/tests.rs b/src/crypto/tests.rs
index 601a5c9..e6829ac 100644
--- a/src/crypto/tests.rs
+++ b/src/crypto/tests.rs
@@ -13,8 +13,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use crate::crypto::FfiSlice;
+use alloc::vec::Vec;
 use hex;
-use kmr_common::crypto::{aes, hmac, AesCmac, ConstTimeEq, Hmac, MonotonicClock, Rng, Sha256};
+use kmr_common::crypto::{
+    aes, hmac, Aes, AesCmac, ConstTimeEq, Hmac, MonotonicClock, Rng, Sha256, SymmetricOperation,
+};
 use kmr_wire::keymint::Digest;
 use log::info;
 
@@ -309,6 +313,358 @@
     info!("test_aes_cmac_256: Success");
 }
 
+/// Test basic [`Aes`] functionality.
+pub fn test_aes<A: Aes>(aes: A) {
+    struct TestCase {
+        input: &'static str,
+        output: &'static str,
+    }
+
+    // AES-128 CTR
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.1
+    let key = aes::Key::new(hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap()).unwrap();
+    let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
+
+    const CTR128_TESTS: &[TestCase] = &[
+        TestCase {
+            input: "6bc1bee22e409f96e93d7e117393172a",
+            output: "874d6191b620e3261bef6864990db6ce",
+        },
+        TestCase {
+            input: "ae2d8a571e03ac9c9eb76fac45af8e51",
+            output: "9806f66b7970fdff8617187bb9fffdff",
+        },
+        TestCase {
+            input: "30c81c46a35ce411e5fbc1191a0a52ef",
+            output: "5ae4df3edbd5d35e5b4f09020db03eab",
+        },
+        TestCase {
+            input: "f69f2445df4f9b17ad2b417be66c3710",
+            output: "1e031dda2fbe03d1792170a0f3009cee",
+        },
+    ];
+
+    let mut encrypt = aes
+        .begin(
+            key.clone().into(),
+            aes::CipherMode::Ctr { nonce: iv.as_slice().try_into().unwrap() },
+            SymmetricOperation::Encrypt,
+        )
+        .unwrap();
+    let mut decrypt = aes
+        .begin(
+            key.clone().into(),
+            aes::CipherMode::Ctr { nonce: iv.as_slice().try_into().unwrap() },
+            SymmetricOperation::Decrypt,
+        )
+        .unwrap();
+
+    for test in CTR128_TESTS.iter() {
+        let input: [u8; 16] = hex::decode(test.input).unwrap().as_slice().try_into().unwrap();
+        let mut output: [u8; 16] = encrypt.update(&input).unwrap().try_into().unwrap();
+        assert_eq!(output, hex::decode(test.output).unwrap().as_slice());
+        output = decrypt.update(&output).unwrap().try_into().unwrap();
+        assert_eq!(input, output);
+    }
+
+    // AES-256 CTR
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.5
+    let key = aes::Key::new(
+        hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(),
+    )
+    .unwrap();
+    let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap();
+
+    const CTR256_TESTS: &[TestCase] = &[
+        TestCase {
+            input: "6bc1bee22e409f96e93d7e117393172a",
+            output: "601ec313775789a5b7a7f504bbf3d228",
+        },
+        TestCase {
+            input: "ae2d8a571e03ac9c9eb76fac45af8e51",
+            output: "f443e3ca4d62b59aca84e990cacaf5c5",
+        },
+        TestCase {
+            input: "30c81c46a35ce411e5fbc1191a0a52ef",
+            output: "2b0930daa23de94ce87017ba2d84988d",
+        },
+        TestCase {
+            input: "f69f2445df4f9b17ad2b417be66c3710",
+            output: "dfc9c58db67aada613c2dd08457941a6",
+        },
+    ];
+
+    let mut encrypt = aes
+        .begin(
+            key.clone().into(),
+            aes::CipherMode::Ctr { nonce: iv.as_slice().try_into().unwrap() },
+            SymmetricOperation::Encrypt,
+        )
+        .unwrap();
+    let mut decrypt = aes
+        .begin(
+            key.clone().into(),
+            aes::CipherMode::Ctr { nonce: iv.as_slice().try_into().unwrap() },
+            SymmetricOperation::Decrypt,
+        )
+        .unwrap();
+
+    for test in CTR256_TESTS.iter() {
+        let input: [u8; 16] = hex::decode(test.input).unwrap().as_slice().try_into().unwrap();
+        let mut output: [u8; 16] = encrypt.update(&input).unwrap().try_into().unwrap();
+        assert_eq!(output, hex::decode(test.output).unwrap().as_slice());
+        output = decrypt.update(&output).unwrap().try_into().unwrap();
+        assert_eq!(input, output);
+    }
+
+    // AES-CBC w/ PKCS#7 padding
+    struct CBCTestCase {
+        key: &'static str,
+        iv: &'static str,
+        input: &'static str,
+        output: &'static str,
+    }
+
+    const CBC_TESTS: &[CBCTestCase] = &[
+        // https://github.com/google/wycheproof/blob/master/testvectors/aes_cbc_pkcs5_test.json
+        // "tcId" : 1
+        CBCTestCase {
+            key: "e34f15c7bd819930fe9d66e0c166e61c",
+            iv: "da9520f7d3520277035173299388bee2",
+            input: "",
+            output: "b10ab60153276941361000414aed0a9d",
+        },
+        // "tcId" : 2
+        CBCTestCase {
+            key: "e09eaa5a3f5e56d279d5e7a03373f6ea",
+            iv: "c9ee3cd746bf208c65ca9e72a266d54f",
+            input: "ef4eab37181f98423e53e947e7050fd0",
+            output: "d1fa697f3e2e04d64f1a0da203813ca5bc226a0b1d42287b2a5b994a66eaf14a",
+        },
+        // "tcId" : 20
+        CBCTestCase {
+            key: "831e664c9e3f0c3094c0b27b9d908eb2",
+            iv: "54f2459e40e002763144f4752cde2fb5",
+            input: "26603bb76dd0a0180791c4ed4d3b058807",
+            output: "8d55dc10584e243f55d2bdbb5758b7fabcd58c8d3785f01c7e3640b2a1dadcd9",
+        },
+        // "tcId" : 124
+        CBCTestCase {
+            key: "612e837843ceae7f61d49625faa7e7494f9253e20cb3adcea686512b043936cd",
+            iv: "9ec7b863ac845cad5e4673da21f5b6a9",
+            input: "cc37fae15f745a2f40e2c8b192f2b38d",
+            output: "299295be47e9f5441fe83a7a811c4aeb2650333e681e69fa6b767d28a6ccf282",
+        },
+        // "tcId" : 128
+        CBCTestCase {
+            key: "ea3b016bdd387dd64d837c71683808f335dbdc53598a4ea8c5f952473fafaf5f",
+            iv: "fae3e2054113f6b3b904aadbfe59655c",
+            input: "6601",
+            output: "b90c326b72eb222ddb4dae47f2bc223c",
+        },
+        // "tcId" : 141
+        CBCTestCase {
+            key: "73216fafd0022d0d6ee27198b2272578fa8f04dd9f44467fbb6437aa45641bf7",
+            iv: "4b74bd981ea9d074757c3e2ef515e5fb",
+            input: "d5247b8f6c3edcbfb1d591d13ece23d2f5",
+            output: "fbea776fb1653635f88e2937ed2450ba4e9063e96d7cdba04928f01cb85492fe",
+        },
+    ];
+
+    for test in CBC_TESTS {
+        let key = aes::Key::new(hex::decode(test.key).unwrap()).unwrap();
+        let iv = hex::decode(test.iv).unwrap();
+
+        let mut encrypt = aes
+            .begin(
+                key.clone().into(),
+                aes::CipherMode::CbcPkcs7Padding { nonce: iv.as_slice().try_into().unwrap() },
+                SymmetricOperation::Encrypt,
+            )
+            .unwrap();
+        let mut encrypt2 = aes
+            .begin(
+                key.clone().into(),
+                aes::CipherMode::CbcPkcs7Padding { nonce: iv.as_slice().try_into().unwrap() },
+                SymmetricOperation::Encrypt,
+            )
+            .unwrap();
+        let mut decrypt = aes
+            .begin(
+                key.clone().into(),
+                aes::CipherMode::CbcPkcs7Padding { nonce: iv.as_slice().try_into().unwrap() },
+                SymmetricOperation::Decrypt,
+            )
+            .unwrap();
+        let mut decrypt2 = aes
+            .begin(
+                key.clone().into(),
+                aes::CipherMode::CbcPkcs7Padding { nonce: iv.as_slice().try_into().unwrap() },
+                SymmetricOperation::Decrypt,
+            )
+            .unwrap();
+
+        let input = hex::decode(test.input).unwrap();
+        let mut encrypted = encrypt.update(input.as_slice()).unwrap();
+        encrypted.extend_from_slice(encrypt.finish().unwrap().as_slice());
+        assert_eq!(encrypted, hex::decode(test.output).unwrap());
+
+        let mut encrypted_by_byte = Vec::<u8>::new();
+        for b in input {
+            encrypted_by_byte
+                .extend_from_slice(encrypt2.update(core::slice::from_ref(&b)).unwrap().as_slice());
+        }
+        encrypted_by_byte.extend_from_slice(encrypt2.finish().unwrap().as_slice());
+        assert_eq!(encrypted_by_byte, hex::decode(test.output).unwrap());
+
+        let mut decrypted = decrypt.update(encrypted.as_slice()).unwrap();
+        decrypted.extend_from_slice(decrypt.finish().unwrap().as_slice());
+        assert_eq!(decrypted, hex::decode(test.input).unwrap());
+
+        let mut decrypted_by_byte = Vec::<u8>::new();
+        for b in encrypted_by_byte {
+            decrypted_by_byte
+                .extend_from_slice(decrypt2.update(core::slice::from_ref(&b)).unwrap().as_slice());
+        }
+        decrypted_by_byte.extend_from_slice(decrypt2.finish().unwrap().as_slice());
+        assert_eq!(decrypted_by_byte, hex::decode(test.input).unwrap());
+    }
+
+    info!("test_aes: Success");
+}
+
+/// Test basic [`Aes`] functionality with AES-GCM.
+pub fn test_aes_gcm<A: Aes>(aes: A) {
+    struct TestCase {
+        key: &'static str,
+        iv: &'static str,
+        aad: &'static str,
+        msg: &'static str,
+        ct: &'static str,
+        tag: &'static str,
+    }
+    // Test vectors from https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    const AES_GCM_TESTS: &[TestCase] = &[
+        TestCase {
+            key: "5b9604fe14eadba931b0ccf34843dab9",
+            iv: "028318abc1824029138141a2",
+            aad: "",
+            msg: "001d0c231287c1182784554ca3a21908",
+            ct: "26073cc1d851beff176384dc9896d5ff",
+            tag: "0a3ea7a5487cb5f7d70fb6c58d038554",
+        },
+        TestCase {
+            key: "5b9604fe14eadba931b0ccf34843dab9",
+            iv: "921d2507fa8007b7bd067d34",
+            aad: "00112233445566778899aabbccddeeff",
+            msg: "001d0c231287c1182784554ca3a21908",
+            ct: "49d8b9783e911913d87094d1f63cc765",
+            tag: "1e348ba07cca2cf04c618cb4d43a5b92",
+        },
+    ];
+    for test in AES_GCM_TESTS {
+        let key = hex::decode(test.key).unwrap();
+        let iv = hex::decode(test.iv).unwrap();
+        assert_eq!(iv.len(), 12); // Only 96-bit nonces supported.
+        let aad = hex::decode(test.aad).unwrap();
+        let msg = hex::decode(test.msg).unwrap();
+        let tag = hex::decode(test.tag).unwrap();
+        assert_eq!(tag.len(), 16); // Test data includes full 128-bit tag
+
+        let aes_key = aes::Key::new(key.clone()).unwrap();
+        let mut op = aes
+            .begin_aead(
+                aes_key.into(),
+                aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() },
+                SymmetricOperation::Encrypt,
+            )
+            .unwrap();
+        op.update_aad(aad.as_ffi_slice()).unwrap();
+        let mut got_ct = op.update(&msg).unwrap();
+        got_ct.extend_from_slice(&op.finish().unwrap());
+        assert_eq!(format!("{}{}", test.ct, test.tag), hex::encode(&got_ct));
+        let aes_key = aes::Key::new(key.clone()).unwrap();
+        let mut op = aes
+            .begin_aead(
+                aes_key.into(),
+                aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() },
+                SymmetricOperation::Decrypt,
+            )
+            .unwrap();
+        op.update_aad(aad.as_ffi_slice()).unwrap();
+        let mut got_pt = op.update(&got_ct).unwrap();
+        got_pt.extend_from_slice(&op.finish().unwrap());
+        assert_eq!(test.msg, hex::encode(&got_pt));
+
+        // One byte at a time
+        let aes_key = aes::Key::new(key.clone()).unwrap();
+        let mut op = aes
+            .begin_aead(
+                aes_key.into(),
+                aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() },
+                SymmetricOperation::Encrypt,
+            )
+            .unwrap();
+
+        op.update_aad(aad.as_ffi_slice()).unwrap();
+        let mut got_ct = Vec::<u8>::new();
+        for b in &msg {
+            got_ct.extend_from_slice(op.update(core::slice::from_ref(&b)).unwrap().as_slice());
+        }
+        got_ct.extend_from_slice(op.finish().unwrap().as_slice());
+        assert_eq!(format!("{}{}", test.ct, test.tag), hex::encode(&got_ct));
+        let aes_key = aes::Key::new(key.clone()).unwrap();
+        let mut op = aes
+            .begin_aead(
+                aes_key.into(),
+                aes::GcmMode::GcmTag16 { nonce: iv.clone().try_into().unwrap() },
+                SymmetricOperation::Decrypt,
+            )
+            .unwrap();
+        op.update_aad(aad.as_ffi_slice()).unwrap();
+        let mut got_pt = Vec::<u8>::new();
+        for b in &got_ct {
+            got_pt.extend_from_slice(op.update(core::slice::from_ref(&b)).unwrap().as_slice());
+        }
+        got_pt.extend_from_slice(op.finish().unwrap().as_slice());
+        assert_eq!(test.msg, hex::encode(&got_pt));
+
+        // Truncated tag should still decrypt.
+        let aes_key = aes::Key::new(key.clone()).unwrap();
+        let mut op = match aes.begin_aead(
+            aes_key.into(),
+            aes::GcmMode::GcmTag12 { nonce: iv.clone().try_into().unwrap() },
+            SymmetricOperation::Decrypt,
+        ) {
+            Ok(c) => c,
+            Err(_) => return,
+        };
+        op.update_aad(aad.as_ffi_slice()).unwrap();
+        let mut got_pt = op.update(&got_ct[..got_ct.len() - 4]).unwrap();
+        got_pt.extend_from_slice(&op.finish().unwrap());
+        assert_eq!(test.msg, hex::encode(&got_pt));
+
+        // Corrupted ciphertext should not decrypt.
+        let aes_key = aes::Key::new(key).unwrap();
+        let mut op = match aes.begin_aead(
+            aes_key.into(),
+            aes::GcmMode::GcmTag12 { nonce: iv.try_into().unwrap() },
+            SymmetricOperation::Decrypt,
+        ) {
+            Ok(c) => c,
+            Err(_) => return,
+        };
+        op.update_aad(aad.as_ffi_slice()).unwrap();
+        let mut corrupt_ct = got_ct.clone();
+        corrupt_ct[0] ^= 0x01;
+        let _corrupt_pt = op.update(&corrupt_ct).unwrap();
+        let result = op.finish();
+        assert!(result.is_err());
+    }
+
+    info!("test_aes_gcm: Success");
+}
+
 pub fn run() {
     info!("crypto::tests::run: Starting");
 
@@ -329,5 +685,9 @@
     test_aes_cmac_192(crate::crypto::mac::AesCmac {});
     test_aes_cmac_256(crate::crypto::mac::AesCmac {});
 
+    test_aes(crate::crypto::aes::Aes {});
+
+    test_aes_gcm(crate::crypto::aes::Aes {});
+
     info!("crypto::tests::run: Done");
 }
diff --git a/src/main.rs b/src/main.rs
index 3c587f4..eb32cf2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -73,7 +73,7 @@
                 rng: Box::new(crypto::rng::Rng),
                 clock: Some(Box::new(crypto::clock::Clock)),
                 compare: Box::new(crypto::eq::Eq),
-                aes: Box::new(kmr_common::crypto::NoOpAes),
+                aes: Box::new(crypto::aes::Aes),
                 des: Box::new(kmr_common::crypto::NoOpDes),
                 hmac: Box::new(crypto::mac::Hmac),
                 rsa: Box::new(kmr_common::crypto::NoOpRsa),