diff --git a/tee/tee_kernel/src/tee/crypto/authenc_aad.rs b/tee/tee_kernel/src/tee/crypto/authenc_aad.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b94cca85138c57ff34e04771abb7a702ef65e5ad
--- /dev/null
+++ b/tee/tee_kernel/src/tee/crypto/authenc_aad.rs
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2025 KylinSoft Co., Ltd.
+// See LICENSES for license details.
+
+//! Buffered additional authenticated data for AEAD (OP-TEE semantics).
+//!
+//! mbedtls allows exactly one `cipher_update_ad` / `cipher_update_ad_ccm` per message.
+//! The kernel accumulates `TEE_AEUpdateAAD` chunks and flushes once before payload
+//! processing, matching OP-TEE's multi-call `crypto_authenc_update_aad` behavior.
+
+use alloc::vec::Vec;
+
+use mbedtls::cipher::raw::Cipher;
+use tee_raw_sys::{
+ TEE_ALG_AES_CCM, TEE_ALG_AES_GCM, TEE_ALG_SM4_CCM, TEE_ALG_SM4_GCM, TEE_ERROR_BAD_PARAMETERS,
+ TEE_ERROR_BAD_STATE,
+};
+
+use crate::tee::TeeResult;
+
+pub(crate) fn cipher_uses_authenc_aad_buffer(algo: u32) -> bool {
+ matches!(
+ algo,
+ TEE_ALG_AES_GCM | TEE_ALG_SM4_GCM | TEE_ALG_AES_CCM | TEE_ALG_SM4_CCM
+ )
+}
+
+fn cipher_uses_ccm(algo: u32) -> bool {
+ matches!(algo, TEE_ALG_AES_CCM | TEE_ALG_SM4_CCM)
+}
+
+/// Accumulated AAD for one AE operation; flushed to mbedtls before the first payload byte.
+pub(crate) struct TeeAuthencAadCtx {
+ buffer: Vec,
+ /// CCM: total AAD length passed to `TEE_AEInit` / `starts_ccm`.
+ expected_len: Option,
+ committed: bool,
+ payload_started: bool,
+ is_ccm: bool,
+}
+
+impl TeeAuthencAadCtx {
+ pub(crate) fn new(algo: u32, aad_len: Option) -> Self {
+ let is_ccm = cipher_uses_ccm(algo);
+ Self {
+ buffer: Vec::new(),
+ expected_len: if is_ccm { aad_len } else { None },
+ committed: false,
+ payload_started: false,
+ is_ccm,
+ }
+ }
+
+ /// Append one `TEE_AEUpdateAAD` chunk (may be called multiple times before payload).
+ pub(crate) fn append_aad(&mut self, data: &[u8]) -> TeeResult {
+ if self.payload_started {
+ return Err(TEE_ERROR_BAD_PARAMETERS);
+ }
+ if self.committed {
+ return Err(TEE_ERROR_BAD_STATE);
+ }
+ if let Some(expected) = self.expected_len {
+ let new_len = self
+ .buffer
+ .len()
+ .checked_add(data.len())
+ .ok_or(TEE_ERROR_BAD_PARAMETERS)?;
+ if new_len > expected {
+ return Err(TEE_ERROR_BAD_PARAMETERS);
+ }
+ }
+ self.buffer.extend_from_slice(data);
+ Ok(())
+ }
+
+ /// Feed accumulated AAD to mbedtls (once). Called before the first payload update/final.
+ pub(crate) fn flush_to_cipher(&mut self, cipher: &mut Cipher) -> TeeResult {
+ if self.committed {
+ return Ok(());
+ }
+ if self.is_ccm {
+ let expected = self.expected_len.ok_or(TEE_ERROR_BAD_PARAMETERS)?;
+ if self.buffer.len() != expected {
+ return Err(TEE_ERROR_BAD_PARAMETERS);
+ }
+ cipher
+ .update_ad_ccm(&self.buffer)
+ .map_err(|_| TEE_ERROR_BAD_PARAMETERS)?;
+ } else if !self.buffer.is_empty() {
+ cipher
+ .update_ad(&self.buffer)
+ .map_err(|_| TEE_ERROR_BAD_PARAMETERS)?;
+ }
+ self.committed = true;
+ Ok(())
+ }
+
+ /// Flush buffered AAD to mbedtls before the first payload byte (idempotent).
+ pub(crate) fn enter_payload_phase(&mut self, cipher: &mut Cipher) -> TeeResult {
+ if self.payload_started {
+ return Ok(());
+ }
+ self.flush_to_cipher(cipher)?;
+ self.payload_started = true;
+ Ok(())
+ }
+}
+
+impl Clone for TeeAuthencAadCtx {
+ fn clone(&self) -> Self {
+ Self {
+ buffer: self.buffer.clone(),
+ expected_len: self.expected_len,
+ committed: self.committed,
+ payload_started: self.payload_started,
+ is_ccm: self.is_ccm,
+ }
+ }
+}
diff --git a/tee/tee_kernel/src/tee/crypto/crypto.rs b/tee/tee_kernel/src/tee/crypto/crypto.rs
index a7df9a8598a8a60b8a9546d2e22d8fb380f91ed9..2ddd2c7008978290df310b314c0817412081bfb6 100644
--- a/tee/tee_kernel/src/tee/crypto/crypto.rs
+++ b/tee/tee_kernel/src/tee/crypto/crypto.rs
@@ -30,6 +30,7 @@ use crate::tee::{
TeeCipherXtsCtx, aes_xts_final_buffered, aes_xts_init, aes_xts_update_buffered,
cipher_uses_aes_xts_kernel,
},
+ authenc_aad::{TeeAuthencAadCtx, cipher_uses_authenc_aad_buffer},
crypto_impl::{
EccAlgoKeyPair, EccComKeyPair, EccKeypair, Sm2DsaKeyPair, Sm2KepKeyPair, Sm2PkeKeyPair,
crypto_ecc_keypair_ops, crypto_ecc_keypair_ops_generate,
@@ -684,10 +685,18 @@ pub(crate) fn crypto_cipher_init(
pending: [0; TeeCipherCtx::PENDING_MAX],
pending_len: 0,
xts,
+ authenc_aad: None,
}));
Ok(())
}
+fn tee_cipher_ctx_flush_authenc_aad(op: &mut TeeCipherCtx) -> TeeResult {
+ let Some(aad) = &mut op.authenc_aad else {
+ return Ok(());
+ };
+ aad.enter_payload_phase(&mut op.cipher)
+}
+
fn cipher_uses_ecb_pending(algo: u32) -> bool {
matches!(
algo,
@@ -791,6 +800,9 @@ pub(crate) fn crypto_cipher_update(
let mut cs_guard = cs.lock();
let algo = cs_guard.algo;
if let CrypCtx::CipherCtx(op) = &mut cs_guard.ctx {
+ if cipher_uses_authenc_aad_buffer(algo) {
+ tee_cipher_ctx_flush_authenc_aad(op)?;
+ }
if let Some(xts) = &mut op.xts {
let stream = xts.stream();
let n = aes_xts_update_buffered(
@@ -914,12 +926,18 @@ pub(crate) fn crypto_authenc_init(
.starts_ccm(payload_len, aad_len, tag_len)
.map_err(|_| TEE_ERROR_BAD_PARAMETERS)?;
}
+ let authenc_aad = if cipher_uses_authenc_aad_buffer(algo) {
+ Some(TeeAuthencAadCtx::new(algo, aad_len))
+ } else {
+ None
+ };
cs_guard.state = CrypState::Initialized;
cs_guard.ctx = CrypCtx::CipherCtx(Box::new(TeeCipherCtx {
cipher,
pending: [0; TeeCipherCtx::PENDING_MAX],
pending_len: 0,
xts: None,
+ authenc_aad,
}));
Ok(())
} else {
@@ -929,18 +947,11 @@ pub(crate) fn crypto_authenc_init(
pub(crate) fn crypto_authenc_update_aad(cs: Arc>, aad: &[u8]) -> TeeResult {
let mut cs_guard = cs.lock();
- let algo = cs_guard.algo;
if let CrypCtx::CipherCtx(op) = &mut cs_guard.ctx {
- match algo {
- TEE_ALG_AES_CCM | TEE_ALG_SM4_CCM => op
- .cipher
- .update_ad_ccm(aad)
- .map_err(|_| TEE_ERROR_BAD_PARAMETERS),
- _ => op
- .cipher
- .update_ad(aad)
- .map_err(|_| TEE_ERROR_BAD_PARAMETERS),
- }
+ let Some(aad_ctx) = &mut op.authenc_aad else {
+ return Err(TEE_ERROR_BAD_PARAMETERS);
+ };
+ aad_ctx.append_aad(aad)
} else {
Err(TEE_ERROR_BAD_PARAMETERS)
}
@@ -955,6 +966,7 @@ pub(crate) fn crypto_authenc_enc_final(
let mut cs_guard = cs.lock();
let mut res: usize = 0;
if let CrypCtx::CipherCtx(op) = &mut cs_guard.ctx {
+ tee_cipher_ctx_flush_authenc_aad(op)?;
if let Some(input) = input {
res = op
.cipher
@@ -979,6 +991,7 @@ pub(crate) fn crypto_authenc_dec_final(
let mut cs_guard = cs.lock();
let mut res: usize = 0;
if let CrypCtx::CipherCtx(op) = &mut cs_guard.ctx {
+ tee_cipher_ctx_flush_authenc_aad(op)?;
if let Some(input) = input {
res = op
.cipher
diff --git a/tee/tee_kernel/src/tee/crypto/mod.rs b/tee/tee_kernel/src/tee/crypto/mod.rs
index 19f357800c9bc40943c730d10a4e879c845f779b..6335fb1087cfc5b84be6661287642f0c671f3d81 100644
--- a/tee/tee_kernel/src/tee/crypto/mod.rs
+++ b/tee/tee_kernel/src/tee/crypto/mod.rs
@@ -2,6 +2,7 @@
// Copyright 2025 KylinSoft Co., Ltd.
// See LICENSES for license details.
pub mod aes_xts;
+pub mod authenc_aad;
#[allow(clippy::module_inception)]
pub mod crypto;
pub mod crypto_impl;
diff --git a/tee/tee_kernel/src/tee/tee_svc_cryp2.rs b/tee/tee_kernel/src/tee/tee_svc_cryp2.rs
index d38a8584d17bf66be54ad28fcf36c5e846163b69..2ca78b533adb98ccd3be8a1fca5c63e901a21d25 100644
--- a/tee/tee_kernel/src/tee/tee_svc_cryp2.rs
+++ b/tee/tee_kernel/src/tee/tee_svc_cryp2.rs
@@ -193,6 +193,8 @@ pub(crate) struct TeeCipherCtx {
pub pending_len: usize,
/// Stateful AES-XTS (mbedtls `cipher_update` does not continue tweak across calls).
pub xts: Option,
+ /// Buffered AAD for GCM/CCM (`mbedtls_cipher_update_ad` is single-shot).
+ pub authenc_aad: Option,
}
impl TeeCipherCtx {
@@ -206,6 +208,7 @@ impl Clone for TeeCipherCtx {
pending: self.pending,
pending_len: self.pending_len,
xts: self.xts.clone(),
+ authenc_aad: self.authenc_aad.clone(),
}
}
}
@@ -3112,6 +3115,104 @@ pub mod tests_cryp {
);
}
+ #[unittest::def_test(custom)]
+ fn test_cryp_sm4_gcm_split_aad_matches_one_shot() {
+ let mut state: u32 = 0;
+ let mut obj_id = TestUserValue::::from_value(0).unwrap();
+ syscall_cryp_obj_alloc(TEE_TYPE_SM4 as _, 128, obj_id.as_user_ref()).unwrap();
+ let obj_id = obj_id.read();
+ syscall_obj_generate_key(obj_id as c_ulong, 128, core::ptr::null(), 0).unwrap();
+
+ let obj = tee_obj_get(obj_id as tee_obj_id_type).unwrap();
+ let mut obj_guard = obj.lock();
+ let key = [
+ 0x69, 0xEE, 0xDF, 0x37, 0x77, 0xE5, 0x94, 0xC3, 0x0E, 0x94, 0xE9, 0xC5, 0xE2, 0xBC,
+ 0xE4, 0x67,
+ ];
+ let mut secret = tee_cryp_obj_secret_wrapper::new(32);
+ secret.set_secret_data(&key).unwrap();
+ let _ = core::mem::replace(&mut obj_guard.attr[0], TeeCryptObj::obj_secret(secret));
+ drop(obj_guard);
+
+ tee_cryp_state_alloc(
+ TEE_ALG_SM4_GCM,
+ TEE_OperationMode::TEE_MODE_ENCRYPT,
+ Some(obj_id as _),
+ None,
+ &mut state,
+ )
+ .unwrap();
+
+ let data: [u8; 64] = [
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+ 0xBB, 0xBB, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ ];
+ let nonce = [
+ 0xA3, 0x33, 0x06, 0x38, 0xA8, 0x09, 0xBA, 0x35, 0x8D, 0x6C, 0x09, 0x8E,
+ ];
+ let ad = [
+ 0xFE, 0xED, 0xFA, 0xCE, 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED, 0xFA, 0xCE, 0xDE, 0xAD,
+ 0xBE, 0xEF, 0xAB, 0xAD, 0xDA, 0xD2,
+ ];
+
+ tee_cryp_authenc_init(state, &nonce, None, None, None).unwrap();
+ tee_cryp_authenc_update_aad(state, &ad[..10]).unwrap();
+ tee_cryp_authenc_update_aad(state, &ad[10..]).unwrap();
+
+ let mut out = [0u8; 80];
+ let mut tag = [0u8; 16];
+ let n = tee_cryp_authenc_update_payload(state, &data, &mut out).unwrap();
+ tee_cryp_authenc_enc_final(state, None, &mut out[n..], &mut tag).unwrap();
+
+ assert_eq!(n, 64);
+ assert_eq!(
+ &out[..64],
+ [
+ 0x0C, 0x29, 0xFC, 0x49, 0x07, 0x11, 0x9F, 0x99, 0xC4, 0x92, 0xE2, 0xFA, 0x7B, 0x63,
+ 0x3F, 0x4E, 0x16, 0x5B, 0xE5, 0x35, 0x85, 0xAB, 0xED, 0x71, 0x8B, 0xA3, 0x9C, 0xAB,
+ 0x80, 0xA0, 0x63, 0x92, 0x73, 0x1E, 0x5C, 0xE6, 0xE3, 0x58, 0x1D, 0xCA, 0xF1, 0x19,
+ 0x03, 0x7D, 0x99, 0x8A, 0x0F, 0x52, 0x2D, 0x68, 0x0A, 0x9D, 0xCB, 0x40, 0x5A, 0xAD,
+ 0xF8, 0x00, 0xC0, 0xC7, 0x98, 0xBA, 0xE3, 0x8A
+ ]
+ );
+ assert_eq!(
+ tag,
+ [
+ 0x19, 0x7F, 0x6C, 0xC5, 0x52, 0x3D, 0xA3, 0x6A, 0x3B, 0x2C, 0x42, 0x92, 0x44, 0xC4,
+ 0x70, 0xAA
+ ]
+ );
+ }
+
+ #[unittest::def_test(custom)]
+ fn test_cryp_authenc_aad_rejected_after_payload() {
+ let mut state: u32 = 0;
+ let mut obj_id = TestUserValue::::from_value(0).unwrap();
+ syscall_cryp_obj_alloc(TEE_TYPE_SM4 as _, 128, obj_id.as_user_ref()).unwrap();
+ let obj_id = obj_id.read();
+ syscall_obj_generate_key(obj_id as c_ulong, 128, core::ptr::null(), 0).unwrap();
+
+ tee_cryp_state_alloc(
+ TEE_ALG_SM4_GCM,
+ TEE_OperationMode::TEE_MODE_ENCRYPT,
+ Some(obj_id as _),
+ None,
+ &mut state,
+ )
+ .unwrap();
+
+ let nonce = [0u8; 12];
+ let data = [0u8; 16];
+ let mut out = [0u8; 32];
+ tee_cryp_authenc_init(state, &nonce, None, None, None).unwrap();
+ tee_cryp_authenc_update_payload(state, &data, &mut out).unwrap();
+ let res = tee_cryp_authenc_update_aad(state, b"late");
+ assert_eq!(res.err(), Some(TEE_ERROR_BAD_PARAMETERS));
+ }
+
#[unittest::def_test(custom)]
fn test_cryp_authenc_requires_ae_state() {
let mut obj_id = TestUserValue::::from_value(0).unwrap();