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:
+58
@@ -1,6 +1,7 @@
|
|||||||
package org.matrix.TEESimulator.interception.keystore.shim
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
import android.hardware.security.keymint.Algorithm
|
import android.hardware.security.keymint.Algorithm
|
||||||
|
import android.hardware.security.keymint.EcCurve
|
||||||
import android.hardware.security.keymint.KeyOrigin
|
import android.hardware.security.keymint.KeyOrigin
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
import android.hardware.security.keymint.KeyParameterValue
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
@@ -20,7 +21,10 @@ import java.security.KeyFactory
|
|||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque
|
||||||
import java.util.concurrent.Executors
|
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.AttestationBuilder
|
||||||
import org.matrix.TEESimulator.attestation.AttestationConstants
|
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
@@ -48,6 +52,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
) : BinderInterceptor() {
|
) : BinderInterceptor() {
|
||||||
|
|
||||||
// --- Data Structures for State Management ---
|
// --- Data Structures for State Management ---
|
||||||
|
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||||
|
|
||||||
data class GeneratedKeyInfo(
|
data class GeneratedKeyInfo(
|
||||||
val keyPair: KeyPair?,
|
val keyPair: KeyPair?,
|
||||||
val secretKey: javax.crypto.SecretKey?,
|
val secretKey: javax.crypto.SecretKey?,
|
||||||
@@ -233,6 +239,20 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
return TransactionResult.SkipTransaction
|
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
|
* 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
|
* 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}."
|
"[TX_ID: $txId] Creating SOFTWARE operation for key ${generatedKeyInfo.nspace}."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trackAndEnforceOpLimit(callingUid, securityLevel)
|
||||||
|
|
||||||
val opParams = data.createTypedArray(KeyParameter.CREATOR)!!
|
val opParams = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
val parsedOpParams = KeyMintAttestation(opParams)
|
val parsedOpParams = KeyMintAttestation(opParams)
|
||||||
val forced = data.readBoolean()
|
val forced = data.readBoolean()
|
||||||
@@ -371,12 +393,14 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
// override purpose from the operation params.
|
// override purpose from the operation params.
|
||||||
val effectiveParams =
|
val effectiveParams =
|
||||||
keyParams.copy(purpose = parsedOpParams.purpose, digest = parsedOpParams.digest.ifEmpty { keyParams.digest })
|
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 =
|
val softwareOperation =
|
||||||
SoftwareOperation(
|
SoftwareOperation(
|
||||||
txId,
|
txId,
|
||||||
generatedKeyInfo.keyPair,
|
generatedKeyInfo.keyPair,
|
||||||
generatedKeyInfo.secretKey,
|
generatedKeyInfo.secretKey,
|
||||||
effectiveParams,
|
effectiveParams,
|
||||||
|
opLatency,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Decrement usage counter on finish; delete key when exhausted.
|
// 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.
|
* 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 {
|
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 {
|
return runCatching {
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
@@ -494,6 +523,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
val isStrongBox = securityLevel == SecurityLevel.STRONGBOX
|
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 {
|
when {
|
||||||
forceGenerate -> doSoftwareGeneration(
|
forceGenerate -> doSoftwareGeneration(
|
||||||
callingUid, keyDescriptor, attestationKey, parsedParams, isAttestKeyRequest
|
callingUid, keyDescriptor, attestationKey, parsedParams, isAttestKeyRequest
|
||||||
@@ -571,9 +605,15 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
generatedKeys[keyId] =
|
generatedKeys[keyId] =
|
||||||
GeneratedKeyInfo(null, secretKey, keyDescriptor.nspace, response, parsedParams)
|
GeneratedKeyInfo(null, secretKey, keyDescriptor.nspace, response, parsedParams)
|
||||||
|
|
||||||
|
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(
|
TeeLatencySimulator.simulateGenerateKeyDelay(
|
||||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return InterceptorUtils.createTypedObjectReply(metadata)
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,9 +653,14 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
TeeLatencySimulator.simulateGenerateKeyDelay(
|
||||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,11 +945,24 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
private const val KEYMINT_INVALID_INPUT_LENGTH = -21
|
private const val KEYMINT_INVALID_INPUT_LENGTH = -21
|
||||||
private const val KEYMINT_INVALID_ARGUMENT = -38
|
private const val KEYMINT_INVALID_ARGUMENT = -38
|
||||||
|
private const val KEYMINT_TOO_MANY_OPERATIONS = -29
|
||||||
private const val INVALID_ARGUMENT = 20
|
private const val INVALID_ARGUMENT = 20
|
||||||
private const val PERMISSION_DENIED = 6
|
private const val PERMISSION_DENIED = 6
|
||||||
private const val SECURE_HW_COMMUNICATION_FAILED = -49
|
private const val SECURE_HW_COMMUNICATION_FAILED = -49
|
||||||
private const val CANNOT_ATTEST_IDS = -66
|
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.
|
// Transaction codes for IKeystoreSecurityLevel interface.
|
||||||
private val GENERATE_KEY_TRANSACTION =
|
private val GENERATE_KEY_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||||
|
|||||||
Reference in New Issue
Block a user