Skip to content

Commit def9d9c

Browse files
committed
feat(interception): add AUTO mode TEE race for G10 attestation consistency
AUTO mode now races TEE hardware against software generation via CompletableFuture. If TEE succeeds, the cert chain is patched and cached in teeResponses before returning, making attestation stress-resilient. If TEE fails, software fallback is used. ConfigurationManager no longer resolves AUTO at config time; it passes Mode.AUTO through to KeyMintSecurityLevelInterceptor for runtime dispatch. shouldPatch() returns true for both PATCH and AUTO modes. TEE status file persistence removed entirely. Aligns handleGenerateKey with upstream PR JingMatrix#157 three-way dispatch: forceGenerate, raceTeePatch, or hardware forwarding with post-patch. Hardware keygen rate limiting removed (replaced by raceTeePatch for AUTO, plain Continue for PATCH). Attest key override in Keystore2Interceptor now patches authorizations and uses null-safe nspace assignment.
1 parent 93f937e commit def9d9c

3 files changed

Lines changed: 108 additions & 104 deletions

File tree

app/src/main/java/org/matrix/TEESimulator/config/ConfigurationManager.kt

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import android.os.IBinder
77
import android.os.ServiceManager
88
import java.io.File
99
import java.util.concurrent.ConcurrentHashMap
10-
import org.matrix.TEESimulator.attestation.DeviceAttestationService
1110
import org.matrix.TEESimulator.logging.SystemLogger
1211
import org.matrix.TEESimulator.pki.KeyBoxManager
1312

@@ -31,15 +30,13 @@ object ConfigurationManager {
3130
// --- Configuration Paths ---
3231
const val CONFIG_PATH = "/data/adb/tricky_store"
3332
private const val TARGET_PACKAGES_FILE = "target.txt"
34-
private const val TEE_STATUS_FILE = "tee_status.txt"
3533
private const val PATCH_LEVEL_FILE = "security_patch.txt"
3634
private const val DEFAULT_KEYBOX_FILE = "keybox.xml"
3735
private val configRoot = File(CONFIG_PATH)
3836

3937
// --- In-Memory Configuration State ---
4038
@Volatile private var packageModes = mapOf<String, Mode>()
4139
@Volatile private var packageKeyboxes = mapOf<String, String>()
42-
@Volatile private var isTeeBroken: Boolean? = null
4340
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
4441
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
4542

@@ -68,8 +65,6 @@ object ConfigurationManager {
6865
// Initial load of all configuration files.
6966
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
7067
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
71-
storeTeeStatus() // Check and store the current TEE status.
72-
7368
// Start watching for any subsequent file changes.
7469
ConfigObserver.startWatching()
7570
SystemLogger.info("Configuration initialized and file observer started.")
@@ -87,33 +82,31 @@ object ConfigurationManager {
8782
return packages.firstNotNullOfOrNull { pkg -> packageKeyboxes[pkg] } ?: DEFAULT_KEYBOX_FILE
8883
}
8984

90-
/** Determines if the certificate for a given UID needs to be patched. */
91-
fun shouldPatch(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.PATCH
85+
fun shouldPatch(uid: Int): Boolean {
86+
val mode = getPackageModeForUid(uid)
87+
return mode == Mode.PATCH || mode == Mode.AUTO
88+
}
9289

9390
/** Determines if a new certificate needs to be generated for a given UID. */
9491
fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE
9592

96-
/** Determines if no operation is needed for a given UID. */
9793
fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null
9894

99-
/** Resolves the operating mode for a given UID based on its packages and the TEE status. */
95+
fun isAutoMode(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.AUTO
96+
10097
private fun getPackageModeForUid(uid: Int): Mode? {
10198
val packages = getPackagesForUid(uid)
10299
if (packages.isEmpty()) return null
103100

104-
// Lazily load TEE status if it hasn't been checked yet.
105-
if (isTeeBroken == null) loadTeeStatus()
106-
107-
// Find the first configured mode for any of the UID's packages.
108101
for (pkg in packages) {
109102
when (packageModes[pkg]) {
110103
Mode.GENERATE -> return Mode.GENERATE
111104
Mode.PATCH -> return Mode.PATCH
112-
Mode.AUTO -> return if (isTeeBroken == true) Mode.GENERATE else Mode.PATCH
113-
null -> continue // No config for this package, check the next one.
105+
Mode.AUTO -> return Mode.AUTO
106+
null -> continue
114107
}
115108
}
116-
return null // No configuration found for this UID.
109+
return null
117110
}
118111

119112
/**
@@ -280,29 +273,6 @@ object ConfigurationManager {
280273
}
281274
}
282275

283-
/** Checks the device's TEE status and writes the result to a file for persistence. */
284-
private fun storeTeeStatus() {
285-
val statusFile = File(configRoot, TEE_STATUS_FILE)
286-
isTeeBroken = !DeviceAttestationService.isTeeFunctional
287-
try {
288-
statusFile.writeText("tee_broken=$isTeeBroken")
289-
SystemLogger.info("TEE status stored: isTeeBroken=$isTeeBroken")
290-
} catch (e: Exception) {
291-
SystemLogger.error("Failed to write TEE status to file.", e)
292-
}
293-
}
294-
295-
/** Loads the TEE status from the file. */
296-
private fun loadTeeStatus() {
297-
val statusFile = File(configRoot, TEE_STATUS_FILE)
298-
isTeeBroken =
299-
if (statusFile.exists()) {
300-
statusFile.readText().trim() == "tee_broken=true"
301-
} else {
302-
null // Status is unknown.
303-
}
304-
}
305-
306276
/**
307277
* A FileObserver that monitors the configuration directory for changes and triggers reloads of
308278
* the relevant settings.

app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,19 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
358358
keyData.second.toTypedArray(),
359359
)
360360
.getOrThrow()
361+
response.metadata.authorizations =
362+
InterceptorUtils.patchAuthorizations(
363+
response.metadata.authorizations,
364+
callingUid,
365+
)
361366

362-
keyDescriptor.nspace = SecureRandom().nextLong()
363-
response.metadata.key.nspace = keyDescriptor.nspace
367+
val newNspace = SecureRandom().nextLong()
368+
response.metadata.key?.let { it.nspace = newNspace }
364369
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
365370
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
366371
keyData.first,
367372
null,
368-
keyDescriptor.nspace,
373+
newNspace,
369374
response,
370375
parsedParameters,
371376
)
@@ -374,7 +379,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
374379
GeneratedKeyPersistence.save(
375380
keyId = keyId,
376381
keyPair = keyData.first,
377-
nspace = keyDescriptor.nspace,
382+
nspace = newNspace,
378383
securityLevel = response.metadata.keySecurityLevel,
379384
certChain = keyData.second,
380385
algorithm = parsedParameters.algorithm,

app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.security.SecureRandom
2020
import java.security.cert.Certificate
2121
import java.security.cert.CertificateFactory
2222
import java.security.spec.PKCS8EncodedKeySpec
23+
import java.util.concurrent.CompletableFuture
2324
import java.util.concurrent.ConcurrentHashMap
2425
import java.util.concurrent.ConcurrentLinkedDeque
2526
import java.util.concurrent.Executors
@@ -116,12 +117,6 @@ class KeyMintSecurityLevelInterceptor(
116117
reply: Parcel?,
117118
resultCode: Int,
118119
): TransactionResult {
119-
if (code == GENERATE_KEY_TRANSACTION && hardwareKeygenTxIds.remove(txId)) {
120-
val remaining = hardwareKeygenCount(callingUid).decrementAndGet()
121-
SystemLogger.info("[TX_ID: $txId] PERMIT_RELEASED uid=$callingUid concurrent_remaining=$remaining result=${if (resultCode == 0) "OK" else "ERROR($resultCode)"}")
122-
}
123-
124-
// We only care about successful transactions.
125120
if (resultCode != 0 || reply == null || InterceptorUtils.hasException(reply))
126121
return TransactionResult.SkipTransaction
127122

@@ -468,38 +463,23 @@ class KeyMintSecurityLevelInterceptor(
468463
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
469464
val isAttestKeyRequest = parsedParams.isAttestKey()
470465

471-
val needsSoftwareGeneration =
466+
val forceGenerate =
472467
ConfigurationManager.shouldGenerate(callingUid) ||
473468
(ConfigurationManager.shouldPatch(callingUid) && isAttestKeyRequest) ||
474469
(attestationKey != null &&
475470
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
476471

477-
if (needsSoftwareGeneration) {
478-
return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
479-
} else if (parsedParams.attestationChallenge != null) {
480-
val windowUsed = hardwareKeygenWindowCount(callingUid)
481-
val concurrentUsed = hardwareKeygenCount(callingUid).get()
472+
val isAuto = ConfigurationManager.isAutoMode(callingUid)
482473

483-
// Sliding window rate limit
484-
if (windowUsed >= MAX_HW_KEYGEN_PER_WINDOW) {
485-
SystemLogger.info("[TX_ID: $txId] RATE_LIMITED uid=$callingUid window=$windowUsed/$MAX_HW_KEYGEN_PER_WINDOW concurrent=$concurrentUsed → software fallback")
486-
return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
487-
}
488-
// Concurrent cap
489-
if (hardwareKeygenCount(callingUid).incrementAndGet() > MAX_CONCURRENT_HW_KEYGEN_PER_UID) {
490-
hardwareKeygenCount(callingUid).decrementAndGet()
491-
SystemLogger.info("[TX_ID: $txId] CONCURRENT_LIMITED uid=$callingUid window=$windowUsed/$MAX_HW_KEYGEN_PER_WINDOW concurrent=${concurrentUsed + 1}/$MAX_CONCURRENT_HW_KEYGEN_PER_UID → software fallback")
492-
return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
474+
when {
475+
forceGenerate -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
476+
isAuto && !teeFunctional -> raceTeePatch(callingUid, keyDescriptor, attestationKey, params, parsedParams, keyId, isAttestKeyRequest)
477+
parsedParams.attestationChallenge != null -> TransactionResult.Continue
478+
else -> {
479+
cleanupKeyData(keyId)
480+
TransactionResult.ContinueAndSkipPost
493481
}
494-
// Both checks passed — commit the window permit and forward to hardware TEE
495-
recordHardwareKeygen(callingUid)
496-
hardwareKeygenTxIds.add(txId)
497-
SystemLogger.info("[TX_ID: $txId] HARDWARE_KEYGEN uid=$callingUid window=${windowUsed + 1}/$MAX_HW_KEYGEN_PER_WINDOW concurrent=${concurrentUsed + 1}/$MAX_CONCURRENT_HW_KEYGEN_PER_UID → forwarding to TEE")
498-
return TransactionResult.Continue
499482
}
500-
501-
cleanupKeyData(keyId)
502-
TransactionResult.ContinueAndSkipPost
503483
}
504484
.getOrElse {
505485
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
@@ -608,6 +588,85 @@ class KeyMintSecurityLevelInterceptor(
608588
return InterceptorUtils.createTypedObjectReply(response.metadata)
609589
}
610590

591+
private fun raceTeePatch(
592+
callingUid: Int,
593+
keyDescriptor: KeyDescriptor,
594+
attestationKey: KeyDescriptor?,
595+
rawParams: Array<KeyParameter>,
596+
parsedParams: KeyMintAttestation,
597+
keyId: KeyIdentifier,
598+
isAttestKeyRequest: Boolean,
599+
): TransactionResult {
600+
SystemLogger.info("AUTO: racing TEE vs software for ${keyDescriptor.alias}")
601+
602+
val teeDescriptor = KeyDescriptor().apply {
603+
domain = keyDescriptor.domain
604+
nspace = keyDescriptor.nspace
605+
alias = keyDescriptor.alias
606+
blob = keyDescriptor.blob
607+
}
608+
val teeAttestKey = attestationKey?.let {
609+
KeyDescriptor().apply {
610+
domain = it.domain
611+
nspace = it.nspace
612+
alias = it.alias
613+
blob = it.blob
614+
}
615+
}
616+
617+
val threadA = CompletableFuture.supplyAsync {
618+
original.generateKey(teeDescriptor, teeAttestKey, rawParams, 0, byteArrayOf())
619+
}
620+
621+
val swDescriptor = KeyDescriptor().apply {
622+
domain = keyDescriptor.domain
623+
nspace = secureRandom.nextLong()
624+
alias = keyDescriptor.alias
625+
blob = keyDescriptor.blob
626+
}
627+
val swKeyId = KeyIdentifier(callingUid, keyDescriptor.alias)
628+
629+
val threadB = CompletableFuture.supplyAsync {
630+
doSoftwareKeyGen(callingUid, swDescriptor, attestationKey, parsedParams, swKeyId, isAttestKeyRequest)
631+
}
632+
633+
return try {
634+
val teeMetadata = threadA.join()
635+
threadB.cancel(true)
636+
teeFunctional = true
637+
SystemLogger.info("AUTO: TEE succeeded for ${keyDescriptor.alias}, marked functional.")
638+
639+
val originalChain = CertificateHelper.getCertificateChain(teeMetadata)
640+
if (originalChain != null && originalChain.size > 1) {
641+
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
642+
CertificateHelper.updateCertificateChain(teeMetadata, newChain).getOrThrow()
643+
teeMetadata.authorizations =
644+
InterceptorUtils.patchAuthorizations(teeMetadata.authorizations, callingUid)
645+
cleanupKeyData(keyId)
646+
patchedChains[keyId] = newChain
647+
}
648+
649+
teeResponses[keyId] = KeyEntryResponse().apply {
650+
this.metadata = teeMetadata
651+
iSecurityLevel = original
652+
}
653+
654+
InterceptorUtils.createTypedObjectReply(teeMetadata)
655+
} catch (_: Exception) {
656+
SystemLogger.info("AUTO: TEE failed for ${keyDescriptor.alias}, using software result.")
657+
try {
658+
threadB.join()
659+
} catch (e: Exception) {
660+
SystemLogger.error("AUTO: both paths failed for ${keyDescriptor.alias}.", e)
661+
val code =
662+
if (e.cause is android.os.ServiceSpecificException)
663+
(e.cause as android.os.ServiceSpecificException).errorCode
664+
else SECURE_HW_COMMUNICATION_FAILED
665+
InterceptorUtils.createServiceSpecificErrorReply(code)
666+
}
667+
}
668+
}
669+
611670
private fun generateAttestedKeyPairNative(
612671
callingUid: Int,
613672
params: KeyMintAttestation,
@@ -811,6 +870,7 @@ class KeyMintSecurityLevelInterceptor(
811870

812871
companion object {
813872
private val secureRandom = SecureRandom()
873+
@Volatile var teeFunctional = false
814874

815875
// Maximum alias length to prevent binder buffer exhaustion (Issue #109)
816876
// Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead
@@ -830,43 +890,12 @@ class KeyMintSecurityLevelInterceptor(
830890
private const val MAX_CONCURRENT_OPS_PER_UID = 15
831891
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
832892
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s
833-
private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2
834-
// Sliding window: max hardware keygen permits per UID within the burst window
835-
private const val MAX_HW_KEYGEN_PER_WINDOW = 2
836-
private const val BURST_WINDOW_MS = 30_000L
837-
private val uidHardwareKeygenCount = ConcurrentHashMap<Int, AtomicInteger>()
838-
private val hardwareKeygenTxIds = ConcurrentHashMap.newKeySet<Long>()
839-
private val uidKeygenTimestamps = ConcurrentHashMap<Int, MutableList<Long>>()
840-
841893
private fun isStrongBoxCapable(params: KeyMintAttestation): Boolean = when (params.algorithm) {
842894
Algorithm.RSA -> params.keySize <= 2048
843895
Algorithm.EC -> params.ecCurve == null || params.ecCurve == EcCurve.P_256
844896
else -> true
845897
}
846898

847-
private fun hardwareKeygenCount(uid: Int): AtomicInteger =
848-
uidHardwareKeygenCount.computeIfAbsent(uid) { AtomicInteger(0) }
849-
850-
private fun hardwareKeygenWindowCount(uid: Int): Int {
851-
val now = System.currentTimeMillis()
852-
val timestamps = uidKeygenTimestamps.computeIfAbsent(uid) { mutableListOf() }
853-
synchronized(timestamps) {
854-
timestamps.removeAll { now - it > BURST_WINDOW_MS }
855-
if (timestamps.isEmpty()) {
856-
uidKeygenTimestamps.remove(uid, timestamps)
857-
uidHardwareKeygenCount.remove(uid)
858-
}
859-
return timestamps.size
860-
}
861-
}
862-
863-
private fun recordHardwareKeygen(uid: Int) {
864-
val timestamps = uidKeygenTimestamps.computeIfAbsent(uid) { mutableListOf() }
865-
synchronized(timestamps) {
866-
timestamps.add(System.currentTimeMillis())
867-
}
868-
}
869-
870899
private val GENERATE_KEY_TRANSACTION =
871900
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
872901
private val IMPORT_KEY_TRANSACTION =

0 commit comments

Comments
 (0)