fix(hardening): restore StrongBox simulation and binder buffer protection
Restores pre-PR157 custom hardening that was lost during the reset: - StrongBox capability check (RSA<=2048, EC=P256) - StrongBox keygen latency floor (250ms) and op latency floor (80ms) - StrongBox concurrent op limit (4 ops in 10s sliding window) - MAX_ALIAS_LENGTH (256KB) binder buffer guard in handleGenerateKey Real StrongBox hardware has these constraints. Without simulation, detectors identify the software shim by its unrealistic performance.
This commit is contained in:
+64
-6
@@ -1,6 +1,7 @@
|
||||
package org.matrix.TEESimulator.interception.keystore.shim
|
||||
|
||||
import android.hardware.security.keymint.Algorithm
|
||||
import android.hardware.security.keymint.EcCurve
|
||||
import android.hardware.security.keymint.KeyOrigin
|
||||
import android.hardware.security.keymint.KeyParameter
|
||||
import android.hardware.security.keymint.KeyParameterValue
|
||||
@@ -20,7 +21,10 @@ import java.security.KeyFactory
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.LockSupport
|
||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||
@@ -48,6 +52,8 @@ class KeyMintSecurityLevelInterceptor(
|
||||
) : BinderInterceptor() {
|
||||
|
||||
// --- Data Structures for State Management ---
|
||||
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||
|
||||
data class GeneratedKeyInfo(
|
||||
val keyPair: KeyPair?,
|
||||
val secretKey: javax.crypto.SecretKey?,
|
||||
@@ -233,6 +239,20 @@ class KeyMintSecurityLevelInterceptor(
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
|
||||
private fun trackAndEnforceOpLimit(callingUid: Int, securityLevel: Int) {
|
||||
if (securityLevel != SecurityLevel.STRONGBOX) return
|
||||
val timestamps = recentOps.computeIfAbsent(callingUid) { ConcurrentLinkedDeque() }
|
||||
val cutoff = System.nanoTime() - STRONGBOX_OP_WINDOW_NS
|
||||
timestamps.removeIf { it < cutoff }
|
||||
if (timestamps.size >= STRONGBOX_MAX_CONCURRENT_OPS) {
|
||||
throw android.os.ServiceSpecificException(
|
||||
KEYMINT_TOO_MANY_OPERATIONS,
|
||||
"StrongBox op limit reached for uid=$callingUid"
|
||||
)
|
||||
}
|
||||
timestamps.addLast(System.nanoTime())
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the `createOperation` transaction. It checks if the operation is for a key that was
|
||||
* generated in software. If so, it creates a software-based operation handler. Otherwise, it
|
||||
@@ -279,6 +299,8 @@ class KeyMintSecurityLevelInterceptor(
|
||||
"[TX_ID: $txId] Creating SOFTWARE operation for key ${generatedKeyInfo.nspace}."
|
||||
)
|
||||
|
||||
trackAndEnforceOpLimit(callingUid, securityLevel)
|
||||
|
||||
val opParams = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val parsedOpParams = KeyMintAttestation(opParams)
|
||||
val forced = data.readBoolean()
|
||||
@@ -371,12 +393,14 @@ class KeyMintSecurityLevelInterceptor(
|
||||
// override purpose from the operation params.
|
||||
val effectiveParams =
|
||||
keyParams.copy(purpose = parsedOpParams.purpose, digest = parsedOpParams.digest.ifEmpty { keyParams.digest })
|
||||
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
|
||||
val softwareOperation =
|
||||
SoftwareOperation(
|
||||
txId,
|
||||
generatedKeyInfo.keyPair,
|
||||
generatedKeyInfo.secretKey,
|
||||
effectiveParams,
|
||||
opLatency,
|
||||
)
|
||||
|
||||
// Decrement usage counter on finish; delete key when exhausted.
|
||||
@@ -424,6 +448,11 @@ class KeyMintSecurityLevelInterceptor(
|
||||
* either generates a key in software or lets the call pass through to the hardware.
|
||||
*/
|
||||
private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult {
|
||||
if (data.dataSize() > MAX_ALIAS_LENGTH) {
|
||||
SystemLogger.warning("Skipping oversized transaction: ${data.dataSize()} bytes")
|
||||
return InterceptorUtils.createErrorReply(KEYMINT_INVALID_INPUT_LENGTH)
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
@@ -494,6 +523,11 @@ class KeyMintSecurityLevelInterceptor(
|
||||
|
||||
val isStrongBox = securityLevel == SecurityLevel.STRONGBOX
|
||||
|
||||
if (isStrongBox && !isStrongBoxCapable(parsedParams)) {
|
||||
SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}), forwarding to HAL")
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
when {
|
||||
forceGenerate -> doSoftwareGeneration(
|
||||
callingUid, keyDescriptor, attestationKey, parsedParams, isAttestKeyRequest
|
||||
@@ -571,9 +605,15 @@ class KeyMintSecurityLevelInterceptor(
|
||||
}
|
||||
generatedKeys[keyId] =
|
||||
GeneratedKeyInfo(null, secretKey, keyDescriptor.nspace, response, parsedParams)
|
||||
TeeLatencySimulator.simulateGenerateKeyDelay(
|
||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
||||
)
|
||||
|
||||
if (securityLevel == SecurityLevel.STRONGBOX) {
|
||||
val delayMs = STRONGBOX_KEYGEN_LATENCY_FLOOR_MS - (System.nanoTime() - genStartNanos) / 1_000_000
|
||||
if (delayMs > 0) LockSupport.parkNanos(delayMs * 1_000_000)
|
||||
} else {
|
||||
TeeLatencySimulator.simulateGenerateKeyDelay(
|
||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
||||
)
|
||||
}
|
||||
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||
}
|
||||
|
||||
@@ -613,9 +653,14 @@ class KeyMintSecurityLevelInterceptor(
|
||||
)
|
||||
}
|
||||
|
||||
TeeLatencySimulator.simulateGenerateKeyDelay(
|
||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
||||
)
|
||||
if (securityLevel == SecurityLevel.STRONGBOX) {
|
||||
val delayMs = STRONGBOX_KEYGEN_LATENCY_FLOOR_MS - (System.nanoTime() - genStartNanos) / 1_000_000
|
||||
if (delayMs > 0) LockSupport.parkNanos(delayMs * 1_000_000)
|
||||
} else {
|
||||
TeeLatencySimulator.simulateGenerateKeyDelay(
|
||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
||||
)
|
||||
}
|
||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||
}
|
||||
|
||||
@@ -900,11 +945,24 @@ class KeyMintSecurityLevelInterceptor(
|
||||
|
||||
private const val KEYMINT_INVALID_INPUT_LENGTH = -21
|
||||
private const val KEYMINT_INVALID_ARGUMENT = -38
|
||||
private const val KEYMINT_TOO_MANY_OPERATIONS = -29
|
||||
private const val INVALID_ARGUMENT = 20
|
||||
private const val PERMISSION_DENIED = 6
|
||||
private const val SECURE_HW_COMMUNICATION_FAILED = -49
|
||||
private const val CANNOT_ATTEST_IDS = -66
|
||||
|
||||
private const val STRONGBOX_KEYGEN_LATENCY_FLOOR_MS = 250L
|
||||
private const val STRONGBOX_OP_LATENCY_FLOOR_MS = 80L
|
||||
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
|
||||
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L
|
||||
private const val MAX_ALIAS_LENGTH = 256 * 1024
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Transaction codes for IKeystoreSecurityLevel interface.
|
||||
private val GENERATE_KEY_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||
|
||||
Reference in New Issue
Block a user