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.
This commit is contained in:
+38
-1
@@ -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,10 +22,40 @@ 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?) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user