From 40519b94ea4c9265fbf487a70e5d0c0762912cdf Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Thu, 4 Jun 2026 13:14:43 +0100 Subject: [PATCH] feat(keystore): vendor-gate non-AEAD updateAad Mirror Duck Detector's OperationErrorPathProbe: real Samsung and Xiaomi-MTK TEEs return success for updateAad on a non-AEAD operation, while other vendors reject it with INVALID_TAG. The shim reads the same Build identity the probe reads and answers accordingly, in both the CryptoPrimitive default (sign/verify) and CipherPrimitive paths. Forward hardening for the #28 detector: the prior unconditional ServiceSpecificException throw already passes the probe on every vendor, so this guards against stricter future probes rather than fixing a current failure. --- .../keystore/shim/SoftwareOperation.kt | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt index 18d063c..e3831a2 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt @@ -8,7 +8,9 @@ import android.hardware.security.keymint.KeyParameterValue import android.hardware.security.keymint.KeyPurpose import android.hardware.security.keymint.PaddingMode import android.hardware.security.keymint.Tag +import android.os.Build import android.os.ServiceSpecificException +import android.os.SystemProperties import android.system.keystore2.IKeystoreOperation import android.system.keystore2.KeyParameters import java.security.KeyPair @@ -20,9 +22,39 @@ import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.logging.KeyMintParameterLogger import org.matrix.TEESimulator.logging.SystemLogger +/** + * Mirrors the per-vendor TEE quirk that Duck Detector's OperationErrorPathProbe checks: real + * Samsung and Xiaomi-MTK TrustZone return success for updateAad on a non-AEAD operation, while + * every other vendor rejects it with a service-specific INVALID_TAG. The module reads the same + * device-identity fields the probe reads, so a forged software operation answers exactly as that + * vendor's real TEE would. + */ +private object VendorQuirks { + private val UPDATE_AAD_ALLOWS_SUCCESS = setOf("samsung") + private val XIAOMI_BRANDS = setOf("xiaomi", "redmi", "poco") + + fun nonAeadUpdateAadSucceeds(): Boolean { + val manufacturer = Build.MANUFACTURER.lowercase() + val brand = Build.BRAND.lowercase() + if (manufacturer in UPDATE_AAD_ALLOWS_SUCCESS || brand in UPDATE_AAD_ALLOWS_SUCCESS) { + return true + } + if (manufacturer != "xiaomi" && brand !in XIAOMI_BRANDS) return false + return isMediaTek() + } + + private fun isMediaTek(): Boolean { + val roHardware = SystemProperties.get("ro.hardware", "") + return roHardware.startsWith("mt") || Build.HARDWARE.startsWith("mt", ignoreCase = true) + } +} + private sealed interface CryptoPrimitive { fun updateAad(aadInput: ByteArray?) { - throw ServiceSpecificException(KeystoreErrorCodes.invalidTag) + // Real Samsung / Xiaomi-MTK TEEs accept updateAad on non-AEAD ops; others reject it. + if (!VendorQuirks.nonAeadUpdateAadSucceeds()) { + throw ServiceSpecificException(KeystoreErrorCodes.invalidTag) + } } fun update(data: ByteArray?): ByteArray? @@ -158,7 +190,12 @@ private class CipherPrimitive( } override fun updateAad(aadInput: ByteArray?) { - if (!isAead) throw ServiceSpecificException(KeystoreErrorCodes.invalidTag) + if (!isAead) { + if (!VendorQuirks.nonAeadUpdateAadSucceeds()) { + throw ServiceSpecificException(KeystoreErrorCodes.invalidTag) + } + return + } if (aadInput != null) cipher.updateAAD(aadInput) }