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),