diff --git a/app/src/main/java/org/matrix/TEESimulator/config/ConfigurationManager.kt b/app/src/main/java/org/matrix/TEESimulator/config/ConfigurationManager.kt index 4d4b8a5..77b698c 100644 --- a/app/src/main/java/org/matrix/TEESimulator/config/ConfigurationManager.kt +++ b/app/src/main/java/org/matrix/TEESimulator/config/ConfigurationManager.kt @@ -7,7 +7,6 @@ import android.os.IBinder import android.os.ServiceManager import java.io.File import java.util.concurrent.ConcurrentHashMap -import org.matrix.TEESimulator.attestation.DeviceAttestationService import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.pki.KeyBoxManager @@ -31,7 +30,6 @@ object ConfigurationManager { // --- Configuration Paths --- const val CONFIG_PATH = "/data/adb/tricky_store" private const val TARGET_PACKAGES_FILE = "target.txt" - private const val TEE_STATUS_FILE = "tee_status.txt" private const val PATCH_LEVEL_FILE = "security_patch.txt" private const val DEFAULT_KEYBOX_FILE = "keybox.xml" private val configRoot = File(CONFIG_PATH) @@ -39,7 +37,6 @@ object ConfigurationManager { // --- In-Memory Configuration State --- @Volatile private var packageModes = mapOf() @Volatile private var packageKeyboxes = mapOf() - @Volatile private var isTeeBroken: Boolean? = null @Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null @Volatile private var packagePatchLevels = mapOf() @@ -68,8 +65,6 @@ object ConfigurationManager { // Initial load of all configuration files. loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE)) loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE)) - storeTeeStatus() // Check and store the current TEE status. - // Start watching for any subsequent file changes. ConfigObserver.startWatching() SystemLogger.info("Configuration initialized and file observer started.") @@ -87,33 +82,31 @@ object ConfigurationManager { return packages.firstNotNullOfOrNull { pkg -> packageKeyboxes[pkg] } ?: DEFAULT_KEYBOX_FILE } - /** Determines if the certificate for a given UID needs to be patched. */ - fun shouldPatch(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.PATCH + fun shouldPatch(uid: Int): Boolean { + val mode = getPackageModeForUid(uid) + return mode == Mode.PATCH || mode == Mode.AUTO + } /** Determines if a new certificate needs to be generated for a given UID. */ fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE - /** Determines if no operation is needed for a given UID. */ fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null - /** Resolves the operating mode for a given UID based on its packages and the TEE status. */ + fun isAutoMode(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.AUTO + private fun getPackageModeForUid(uid: Int): Mode? { val packages = getPackagesForUid(uid) if (packages.isEmpty()) return null - // Lazily load TEE status if it hasn't been checked yet. - if (isTeeBroken == null) loadTeeStatus() - - // Find the first configured mode for any of the UID's packages. for (pkg in packages) { when (packageModes[pkg]) { Mode.GENERATE -> return Mode.GENERATE Mode.PATCH -> return Mode.PATCH - Mode.AUTO -> return if (isTeeBroken == true) Mode.GENERATE else Mode.PATCH - null -> continue // No config for this package, check the next one. + Mode.AUTO -> return Mode.AUTO + null -> continue } } - return null // No configuration found for this UID. + return null } /** @@ -280,29 +273,6 @@ object ConfigurationManager { } } - /** Checks the device's TEE status and writes the result to a file for persistence. */ - private fun storeTeeStatus() { - val statusFile = File(configRoot, TEE_STATUS_FILE) - isTeeBroken = !DeviceAttestationService.isTeeFunctional - try { - statusFile.writeText("tee_broken=$isTeeBroken") - SystemLogger.info("TEE status stored: isTeeBroken=$isTeeBroken") - } catch (e: Exception) { - SystemLogger.error("Failed to write TEE status to file.", e) - } - } - - /** Loads the TEE status from the file. */ - private fun loadTeeStatus() { - val statusFile = File(configRoot, TEE_STATUS_FILE) - isTeeBroken = - if (statusFile.exists()) { - statusFile.readText().trim() == "tee_broken=true" - } else { - null // Status is unknown. - } - } - /** * A FileObserver that monitors the configuration directory for changes and triggers reloads of * the relevant settings. diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt index 4bf9c9e..1e61f24 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt @@ -358,14 +358,19 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { keyData.second.toTypedArray(), ) .getOrThrow() + response.metadata.authorizations = + InterceptorUtils.patchAuthorizations( + response.metadata.authorizations, + callingUid, + ) - keyDescriptor.nspace = SecureRandom().nextLong() - response.metadata.key.nspace = keyDescriptor.nspace + val newNspace = SecureRandom().nextLong() + response.metadata.key?.let { it.nspace = newNspace } KeyMintSecurityLevelInterceptor.generatedKeys[keyId] = KeyMintSecurityLevelInterceptor.GeneratedKeyInfo( keyData.first, null, - keyDescriptor.nspace, + newNspace, response, parsedParameters, ) @@ -374,7 +379,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { GeneratedKeyPersistence.save( keyId = keyId, keyPair = keyData.first, - nspace = keyDescriptor.nspace, + nspace = newNspace, securityLevel = response.metadata.keySecurityLevel, certChain = keyData.second, algorithm = parsedParameters.algorithm, diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 57209f4..e198d9c 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -20,6 +20,7 @@ import java.security.SecureRandom import java.security.cert.Certificate import java.security.cert.CertificateFactory import java.security.spec.PKCS8EncodedKeySpec +import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.Executors @@ -116,12 +117,6 @@ class KeyMintSecurityLevelInterceptor( reply: Parcel?, resultCode: Int, ): TransactionResult { - if (code == GENERATE_KEY_TRANSACTION && hardwareKeygenTxIds.remove(txId)) { - val remaining = hardwareKeygenCount(callingUid).decrementAndGet() - SystemLogger.info("[TX_ID: $txId] PERMIT_RELEASED uid=$callingUid concurrent_remaining=$remaining result=${if (resultCode == 0) "OK" else "ERROR($resultCode)"}") - } - - // We only care about successful transactions. if (resultCode != 0 || reply == null || InterceptorUtils.hasException(reply)) return TransactionResult.SkipTransaction @@ -468,38 +463,23 @@ class KeyMintSecurityLevelInterceptor( val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val isAttestKeyRequest = parsedParams.isAttestKey() - val needsSoftwareGeneration = + val forceGenerate = ConfigurationManager.shouldGenerate(callingUid) || (ConfigurationManager.shouldPatch(callingUid) && isAttestKeyRequest) || (attestationKey != null && isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias))) - if (needsSoftwareGeneration) { - return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest) - } else if (parsedParams.attestationChallenge != null) { - val windowUsed = hardwareKeygenWindowCount(callingUid) - val concurrentUsed = hardwareKeygenCount(callingUid).get() + val isAuto = ConfigurationManager.isAutoMode(callingUid) - // Sliding window rate limit - if (windowUsed >= MAX_HW_KEYGEN_PER_WINDOW) { - SystemLogger.info("[TX_ID: $txId] RATE_LIMITED uid=$callingUid window=$windowUsed/$MAX_HW_KEYGEN_PER_WINDOW concurrent=$concurrentUsed → software fallback") - return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest) + when { + forceGenerate -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest) + isAuto && !teeFunctional -> raceTeePatch(callingUid, keyDescriptor, attestationKey, params, parsedParams, keyId, isAttestKeyRequest) + parsedParams.attestationChallenge != null -> TransactionResult.Continue + else -> { + cleanupKeyData(keyId) + TransactionResult.ContinueAndSkipPost } - // Concurrent cap - if (hardwareKeygenCount(callingUid).incrementAndGet() > MAX_CONCURRENT_HW_KEYGEN_PER_UID) { - hardwareKeygenCount(callingUid).decrementAndGet() - 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") - return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest) - } - // Both checks passed — commit the window permit and forward to hardware TEE - recordHardwareKeygen(callingUid) - hardwareKeygenTxIds.add(txId) - 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") - return TransactionResult.Continue } - - cleanupKeyData(keyId) - TransactionResult.ContinueAndSkipPost } .getOrElse { SystemLogger.error("Error during generateKey handling for UID $callingUid.", it) @@ -608,6 +588,85 @@ class KeyMintSecurityLevelInterceptor( return InterceptorUtils.createTypedObjectReply(response.metadata) } + private fun raceTeePatch( + callingUid: Int, + keyDescriptor: KeyDescriptor, + attestationKey: KeyDescriptor?, + rawParams: Array, + parsedParams: KeyMintAttestation, + keyId: KeyIdentifier, + isAttestKeyRequest: Boolean, + ): TransactionResult { + SystemLogger.info("AUTO: racing TEE vs software for ${keyDescriptor.alias}") + + val teeDescriptor = KeyDescriptor().apply { + domain = keyDescriptor.domain + nspace = keyDescriptor.nspace + alias = keyDescriptor.alias + blob = keyDescriptor.blob + } + val teeAttestKey = attestationKey?.let { + KeyDescriptor().apply { + domain = it.domain + nspace = it.nspace + alias = it.alias + blob = it.blob + } + } + + val threadA = CompletableFuture.supplyAsync { + original.generateKey(teeDescriptor, teeAttestKey, rawParams, 0, byteArrayOf()) + } + + val swDescriptor = KeyDescriptor().apply { + domain = keyDescriptor.domain + nspace = secureRandom.nextLong() + alias = keyDescriptor.alias + blob = keyDescriptor.blob + } + val swKeyId = KeyIdentifier(callingUid, keyDescriptor.alias) + + val threadB = CompletableFuture.supplyAsync { + doSoftwareKeyGen(callingUid, swDescriptor, attestationKey, parsedParams, swKeyId, isAttestKeyRequest) + } + + return try { + val teeMetadata = threadA.join() + threadB.cancel(true) + teeFunctional = true + SystemLogger.info("AUTO: TEE succeeded for ${keyDescriptor.alias}, marked functional.") + + val originalChain = CertificateHelper.getCertificateChain(teeMetadata) + if (originalChain != null && originalChain.size > 1) { + val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) + CertificateHelper.updateCertificateChain(teeMetadata, newChain).getOrThrow() + teeMetadata.authorizations = + InterceptorUtils.patchAuthorizations(teeMetadata.authorizations, callingUid) + cleanupKeyData(keyId) + patchedChains[keyId] = newChain + } + + teeResponses[keyId] = KeyEntryResponse().apply { + this.metadata = teeMetadata + iSecurityLevel = original + } + + InterceptorUtils.createTypedObjectReply(teeMetadata) + } catch (_: Exception) { + SystemLogger.info("AUTO: TEE failed for ${keyDescriptor.alias}, using software result.") + try { + threadB.join() + } catch (e: Exception) { + SystemLogger.error("AUTO: both paths failed for ${keyDescriptor.alias}.", e) + val code = + if (e.cause is android.os.ServiceSpecificException) + (e.cause as android.os.ServiceSpecificException).errorCode + else SECURE_HW_COMMUNICATION_FAILED + InterceptorUtils.createServiceSpecificErrorReply(code) + } + } + } + private fun generateAttestedKeyPairNative( callingUid: Int, params: KeyMintAttestation, @@ -811,6 +870,7 @@ class KeyMintSecurityLevelInterceptor( companion object { private val secureRandom = SecureRandom() + @Volatile var teeFunctional = false // Maximum alias length to prevent binder buffer exhaustion (Issue #109) // Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead @@ -830,43 +890,12 @@ class KeyMintSecurityLevelInterceptor( private const val MAX_CONCURRENT_OPS_PER_UID = 15 private const val STRONGBOX_MAX_CONCURRENT_OPS = 4 private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s - private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2 - // Sliding window: max hardware keygen permits per UID within the burst window - private const val MAX_HW_KEYGEN_PER_WINDOW = 2 - private const val BURST_WINDOW_MS = 30_000L - private val uidHardwareKeygenCount = ConcurrentHashMap() - private val hardwareKeygenTxIds = ConcurrentHashMap.newKeySet() - private val uidKeygenTimestamps = ConcurrentHashMap>() - private fun isStrongBoxCapable(params: KeyMintAttestation): Boolean = when (params.algorithm) { Algorithm.RSA -> params.keySize <= 2048 Algorithm.EC -> params.ecCurve == null || params.ecCurve == EcCurve.P_256 else -> true } - private fun hardwareKeygenCount(uid: Int): AtomicInteger = - uidHardwareKeygenCount.computeIfAbsent(uid) { AtomicInteger(0) } - - private fun hardwareKeygenWindowCount(uid: Int): Int { - val now = System.currentTimeMillis() - val timestamps = uidKeygenTimestamps.computeIfAbsent(uid) { mutableListOf() } - synchronized(timestamps) { - timestamps.removeAll { now - it > BURST_WINDOW_MS } - if (timestamps.isEmpty()) { - uidKeygenTimestamps.remove(uid, timestamps) - uidHardwareKeygenCount.remove(uid) - } - return timestamps.size - } - } - - private fun recordHardwareKeygen(uid: Int) { - val timestamps = uidKeygenTimestamps.computeIfAbsent(uid) { mutableListOf() } - synchronized(timestamps) { - timestamps.add(System.currentTimeMillis()) - } - } - private val GENERATE_KEY_TRANSACTION = InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey") private val IMPORT_KEY_TRANSACTION =