Compare commits
11
Commits
v5.1-153
...
v5.1.1-164
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e74ebbe82 | ||
|
|
315f41f434 | ||
|
|
9be0874e93 | ||
|
|
25dbddf733 | ||
|
|
9a7011eb5e | ||
|
|
b63570a3e2 | ||
|
|
3d7fd427a6 | ||
|
|
b2bf0ce599 | ||
|
|
63789ba29d | ||
|
|
40c7b6bd15 | ||
|
|
1df30b9345 |
@@ -29,7 +29,7 @@ val gitExecutor = objects.newInstance(GitExecutor::class.java)
|
|||||||
|
|
||||||
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
||||||
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
||||||
val verName = "v5.1"
|
val verName = "v5.1.1"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
|
|||||||
@@ -516,6 +516,11 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (params.callerNonce == true) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_CALLER_NONCE, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
if (params.unlockedDeviceRequired == true) {
|
if (params.unlockedDeviceRequired == true) {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ data class KeyMintAttestation(
|
|||||||
|
|
||||||
/** Maps to AOSP field = Integer */
|
/** Maps to AOSP field = Integer */
|
||||||
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? =
|
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? =
|
||||||
this.find { it.tag == tag }?.value?.boolValue
|
if (this.any { it.tag == tag }) true else null
|
||||||
|
|
||||||
/** Maps to AOSP field = Integer */
|
/** Maps to AOSP field = Integer */
|
||||||
private fun Array<KeyParameter>.findInteger(tag: Int): Int? =
|
private fun Array<KeyParameter>.findInteger(tag: Int): Int? =
|
||||||
|
|||||||
@@ -250,9 +250,14 @@ object ConfigurationManager {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse global and per-package configurations.
|
var newGlobalLevel = parseLines(contextLines[""])
|
||||||
val newGlobalLevel = parseLines(contextLines[""])
|
contextLines.remove("")
|
||||||
contextLines.remove("") // Remove global context to iterate over packages next
|
|
||||||
|
// system=prop means all components should derive from device props
|
||||||
|
if (newGlobalLevel?.system.equals("prop", ignoreCase = true)) {
|
||||||
|
SystemLogger.info("system=prop: forcing boot/vendor to derive from device props")
|
||||||
|
newGlobalLevel = newGlobalLevel?.copy(boot = "prop", vendor = "prop")
|
||||||
|
}
|
||||||
|
|
||||||
for ((pkg, lines) in contextLines) {
|
for ((pkg, lines) in contextLines) {
|
||||||
parseLines(lines)?.let { newPackageLevels[pkg] = it }
|
parseLines(lines)?.let { newPackageLevels[pkg] = it }
|
||||||
@@ -282,8 +287,10 @@ object ConfigurationManager {
|
|||||||
|
|
||||||
val file = if (event != DELETE) File(configRoot, path) else null
|
val file = if (event != DELETE) File(configRoot, path) else null
|
||||||
when (path) {
|
when (path) {
|
||||||
TARGET_PACKAGES_FILE -> loadTargetPackages(file!!)
|
TARGET_PACKAGES_FILE -> file?.let { loadTargetPackages(it) }
|
||||||
PATCH_LEVEL_FILE -> loadPatchLevelConfig(file!!)
|
?: SystemLogger.warning("$TARGET_PACKAGES_FILE was deleted.")
|
||||||
|
PATCH_LEVEL_FILE -> file?.let { loadPatchLevelConfig(it) }
|
||||||
|
?: SystemLogger.warning("$PATCH_LEVEL_FILE was deleted.")
|
||||||
// Any change to an XML file is assumed to be a keybox.
|
// Any change to an XML file is assumed to be a keybox.
|
||||||
// The cache in KeyBoxManager will handle reloading it on its next use.
|
// The cache in KeyBoxManager will handle reloading it on its next use.
|
||||||
else ->
|
else ->
|
||||||
|
|||||||
+9
-4
@@ -61,8 +61,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys whose certs were updated via updateSubcomponent; skip re-patching on getKeyEntry.
|
|
||||||
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
private val deletedSoftwareKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
|
||||||
// Backdoor binder for registering new interceptors at runtime.
|
// Backdoor binder for registering new interceptors at runtime.
|
||||||
private var backdoorBinder: IBinder? = null
|
private var backdoorBinder: IBinder? = null
|
||||||
@@ -225,6 +225,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId)
|
KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId)
|
||||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
||||||
if (isSoftwareKey) {
|
if (isSoftwareKey) {
|
||||||
|
deletedSoftwareKeys.add(keyId)
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response."
|
"[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response."
|
||||||
)
|
)
|
||||||
@@ -239,6 +240,10 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||||
|
|
||||||
|
if (deletedSoftwareKeys.remove(keyId)) {
|
||||||
|
return InterceptorUtils.createErrorReply(7) // KEY_NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
val response =
|
val response =
|
||||||
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
||||||
?: return TransactionResult.Continue
|
?: return TransactionResult.Continue
|
||||||
@@ -387,13 +392,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
callingUid,
|
callingUid,
|
||||||
)
|
)
|
||||||
|
|
||||||
val newNspace = SecureRandom().nextLong()
|
val key = response.metadata.key!!
|
||||||
response.metadata.key?.let { it.nspace = newNspace }
|
key.nspace = SecureRandom().nextLong()
|
||||||
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
||||||
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
||||||
keyData.first,
|
keyData.first,
|
||||||
null,
|
null,
|
||||||
newNspace,
|
key.nspace,
|
||||||
response,
|
response,
|
||||||
parsedParameters,
|
parsedParameters,
|
||||||
)
|
)
|
||||||
|
|||||||
+89
-18
@@ -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
|
||||||
@@ -47,7 +51,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
private val securityLevel: Int,
|
private val securityLevel: Int,
|
||||||
) : BinderInterceptor() {
|
) : BinderInterceptor() {
|
||||||
|
|
||||||
// --- Data Structures for State Management ---
|
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||||
|
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||||
|
|
||||||
data class GeneratedKeyInfo(
|
data class GeneratedKeyInfo(
|
||||||
val keyPair: KeyPair?,
|
val keyPair: KeyPair?,
|
||||||
val secretKey: javax.crypto.SecretKey?,
|
val secretKey: javax.crypto.SecretKey?,
|
||||||
@@ -233,11 +239,31 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun trackAndEnforceOpLimit(callingUid: Int, securityLevel: Int) {
|
||||||
* Handles the `createOperation` transaction. It checks if the operation is for a key that was
|
if (securityLevel != SecurityLevel.STRONGBOX) return
|
||||||
* generated in software. If so, it creates a software-based operation handler. Otherwise, it
|
val timestamps = recentOps.computeIfAbsent(callingUid) { ConcurrentLinkedDeque() }
|
||||||
* lets the call proceed to the real hardware service.
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pruneOpsForUid(callingUid: Int, newOp: SoftwareOperation) {
|
||||||
|
val ops = activeOps.computeIfAbsent(callingUid) { ConcurrentLinkedDeque() }
|
||||||
|
ops.removeIf { it.isFinalized }
|
||||||
|
while (ops.size >= MAX_CONCURRENT_OPS_PER_UID) {
|
||||||
|
val oldest = ops.pollFirst() ?: break
|
||||||
|
oldest.abort()
|
||||||
|
SystemLogger.debug("Pruned oldest op for uid=$callingUid (LRU eviction)")
|
||||||
|
}
|
||||||
|
ops.addLast(newOp)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCreateOperation(
|
private fun handleCreateOperation(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
@@ -279,6 +305,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()
|
||||||
@@ -300,7 +328,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
(isAsymmetric &&
|
(isAsymmetric &&
|
||||||
(requestedPurpose == KeyPurpose.VERIFY ||
|
(requestedPurpose == KeyPurpose.VERIFY ||
|
||||||
requestedPurpose == KeyPurpose.ENCRYPT)) ||
|
requestedPurpose == KeyPurpose.ENCRYPT)) ||
|
||||||
(requestedPurpose == KeyPurpose.AGREE_KEY && algorithm != Algorithm.EC)
|
(requestedPurpose == KeyPurpose.AGREE_KEY && algorithm != Algorithm.EC) ||
|
||||||
|
(algorithm == Algorithm.EC && requestedPurpose == KeyPurpose.DECRYPT)
|
||||||
if (unsupported) {
|
if (unsupported) {
|
||||||
return InterceptorUtils.createServiceSpecificErrorReply(
|
return InterceptorUtils.createServiceSpecificErrorReply(
|
||||||
KeystoreErrorCode.UNSUPPORTED_PURPOSE
|
KeystoreErrorCode.UNSUPPORTED_PURPOSE
|
||||||
@@ -371,13 +400,16 @@ 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,
|
||||||
)
|
)
|
||||||
|
pruneOpsForUid(callingUid, softwareOperation)
|
||||||
|
|
||||||
// Decrement usage counter on finish; delete key when exhausted.
|
// Decrement usage counter on finish; delete key when exhausted.
|
||||||
if (keyParams.usageCountLimit != null && resolvedKeyId != null) {
|
if (keyParams.usageCountLimit != null && resolvedKeyId != null) {
|
||||||
@@ -424,6 +456,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 +531,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 +613,15 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
generatedKeys[keyId] =
|
generatedKeys[keyId] =
|
||||||
GeneratedKeyInfo(null, secretKey, keyDescriptor.nspace, response, parsedParams)
|
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)
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,9 +661,14 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
TeeLatencySimulator.simulateGenerateKeyDelay(
|
if (securityLevel == SecurityLevel.STRONGBOX) {
|
||||||
parsedParams.algorithm, System.nanoTime() - genStartNanos
|
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)
|
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,11 +953,25 @@ 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 const val MAX_CONCURRENT_OPS_PER_UID = 15
|
||||||
|
|
||||||
|
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")
|
||||||
@@ -998,7 +1065,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clears all cached keys.
|
fun invalidatePatchedChains(reason: String? = null) {
|
||||||
|
val count = patchedChains.size
|
||||||
|
if (count == 0) return
|
||||||
|
patchedChains.clear()
|
||||||
|
SystemLogger.info("Invalidated $count patched cert chains${reason?.let { " due to $it" } ?: ""}.")
|
||||||
|
}
|
||||||
|
|
||||||
fun clearAllGeneratedKeys(reason: String? = null) {
|
fun clearAllGeneratedKeys(reason: String? = null) {
|
||||||
val count = generatedKeys.size
|
val count = generatedKeys.size
|
||||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
@@ -1069,10 +1142,8 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.noAuthRequired != null) {
|
if (this.noAuthRequired != false) {
|
||||||
authList.add(
|
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||||
createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(this.noAuthRequired))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.callerNonce == true) {
|
if (this.callerNonce == true) {
|
||||||
|
|||||||
+56
-34
@@ -15,36 +15,38 @@ import android.system.keystore2.KeyParameters
|
|||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
|
import java.util.concurrent.locks.LockSupport
|
||||||
|
import javax.crypto.BadPaddingException
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.IllegalBlockSizeException
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
/** Keystore2 error codes for ServiceSpecificException. Negative = KeyMint, positive = Keystore. */
|
|
||||||
internal object KeystoreErrorCode {
|
internal object KeystoreErrorCode {
|
||||||
const val INVALID_OPERATION_HANDLE = -28
|
val INVALID_OPERATION_HANDLE: Int by lazy { resolve("ErrorCode", "INVALID_OPERATION_HANDLE", -28) }
|
||||||
const val VERIFICATION_FAILED = -30
|
val VERIFICATION_FAILED: Int by lazy { resolve("ErrorCode", "VERIFICATION_FAILED", -30) }
|
||||||
const val UNSUPPORTED_PURPOSE = -2
|
val UNSUPPORTED_PURPOSE: Int by lazy { resolve("ErrorCode", "UNSUPPORTED_PURPOSE", -2) }
|
||||||
const val INCOMPATIBLE_PURPOSE = -3
|
val INCOMPATIBLE_PURPOSE: Int by lazy { resolve("ErrorCode", "INCOMPATIBLE_PURPOSE", -3) }
|
||||||
const val SYSTEM_ERROR = 4
|
val INVALID_ARGUMENT: Int by lazy { resolve("ErrorCode", "INVALID_ARGUMENT", -38) }
|
||||||
const val TOO_MUCH_DATA = 21
|
val INVALID_TAG: Int by lazy { resolve("ErrorCode", "INVALID_TAG", -40) }
|
||||||
const val KEY_EXPIRED = -25
|
val INVALID_INPUT_LENGTH: Int by lazy { resolve("ErrorCode", "INVALID_INPUT_LENGTH", -21) }
|
||||||
const val KEY_NOT_YET_VALID = -24
|
val INCOMPATIBLE_KEY: Int by lazy { resolve("ErrorCode", "INCOMPATIBLE_KEY", -31) }
|
||||||
|
val INCOMPATIBLE_ALGORITHM: Int by lazy { resolve("ErrorCode", "INCOMPATIBLE_ALGORITHM", -18) }
|
||||||
|
val KEY_EXPIRED: Int by lazy { resolve("ErrorCode", "KEY_EXPIRED", -25) }
|
||||||
|
val KEY_NOT_YET_VALID: Int by lazy { resolve("ErrorCode", "KEY_NOT_YET_VALID", -24) }
|
||||||
|
val CALLER_NONCE_PROHIBITED: Int by lazy { resolve("ErrorCode", "CALLER_NONCE_PROHIBITED", -55) }
|
||||||
|
val UNKNOWN_ERROR: Int by lazy { resolve("ErrorCode", "UNKNOWN_ERROR", -1000) }
|
||||||
|
val SYSTEM_ERROR: Int by lazy { resolve("ResponseCode", "SYSTEM_ERROR", 4, keystore = true) }
|
||||||
|
val TOO_MUCH_DATA: Int by lazy { resolve("ResponseCode", "TOO_MUCH_DATA", 21, keystore = true) }
|
||||||
|
val PERMISSION_DENIED: Int by lazy { resolve("ResponseCode", "PERMISSION_DENIED", 6, keystore = true) }
|
||||||
|
val KEY_NOT_FOUND: Int by lazy { resolve("ResponseCode", "KEY_NOT_FOUND", 7, keystore = true) }
|
||||||
|
|
||||||
/** KeyMint ErrorCode::CALLER_NONCE_PROHIBITED */
|
private fun resolve(enumName: String, field: String, fallback: Int, keystore: Boolean = false): Int {
|
||||||
const val CALLER_NONCE_PROHIBITED = -55
|
val pkg = if (keystore) "android.system.keystore2" else "android.hardware.security.keymint"
|
||||||
|
return runCatching { Class.forName("$pkg.$enumName").getField(field).getInt(null) }
|
||||||
/** KeyMint ErrorCode::INVALID_ARGUMENT */
|
.getOrDefault(fallback)
|
||||||
const val INVALID_ARGUMENT = -38
|
}
|
||||||
|
|
||||||
/** KeyMint ErrorCode::INVALID_TAG */
|
|
||||||
const val INVALID_TAG = -40
|
|
||||||
|
|
||||||
/** Keystore2 ResponseCode::PERMISSION_DENIED */
|
|
||||||
const val PERMISSION_DENIED = 6
|
|
||||||
|
|
||||||
/** Keystore2 ResponseCode::KEY_NOT_FOUND */
|
|
||||||
const val KEY_NOT_FOUND = 7
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A sealed interface to represent the different cryptographic operations we can perform.
|
// A sealed interface to represent the different cryptographic operations we can perform.
|
||||||
@@ -99,8 +101,9 @@ private object JcaAlgorithmMapper {
|
|||||||
when (params.blockMode.firstOrNull()) {
|
when (params.blockMode.firstOrNull()) {
|
||||||
BlockMode.ECB -> "ECB"
|
BlockMode.ECB -> "ECB"
|
||||||
BlockMode.CBC -> "CBC"
|
BlockMode.CBC -> "CBC"
|
||||||
|
BlockMode.CTR -> "CTR"
|
||||||
BlockMode.GCM -> "GCM"
|
BlockMode.GCM -> "GCM"
|
||||||
else -> "ECB" // Default for RSA
|
else -> "ECB"
|
||||||
}
|
}
|
||||||
val padding =
|
val padding =
|
||||||
when (params.padding.firstOrNull()) {
|
when (params.padding.firstOrNull()) {
|
||||||
@@ -183,8 +186,10 @@ private class CipherPrimitive(
|
|||||||
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
||||||
init(opMode, cryptoKey)
|
init(opMode, cryptoKey)
|
||||||
}
|
}
|
||||||
|
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
|
||||||
|
|
||||||
override fun updateAad(data: ByteArray?) {
|
override fun updateAad(data: ByteArray?) {
|
||||||
|
if (!isAead) throw ServiceSpecificException(KeystoreErrorCode.INVALID_TAG)
|
||||||
if (data != null) cipher.updateAAD(data)
|
if (data != null) cipher.updateAAD(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,11 +252,13 @@ class SoftwareOperation(
|
|||||||
keyPair: KeyPair?,
|
keyPair: KeyPair?,
|
||||||
secretKey: javax.crypto.SecretKey?,
|
secretKey: javax.crypto.SecretKey?,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
|
private val latencyFloorMs: Long = 0L,
|
||||||
var onFinishCallback: (() -> Unit)? = null,
|
var onFinishCallback: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
private val primitive: CryptoPrimitive
|
private val primitive: CryptoPrimitive
|
||||||
|
|
||||||
@Volatile private var finalized = false
|
@Volatile var isFinalized = false
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val purpose = params.purpose.firstOrNull()
|
val purpose = params.purpose.firstOrNull()
|
||||||
@@ -288,7 +295,7 @@ class SoftwareOperation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkActive() {
|
private fun checkActive() {
|
||||||
if (finalized)
|
if (isFinalized)
|
||||||
throw ServiceSpecificException(
|
throw ServiceSpecificException(
|
||||||
KeystoreErrorCode.INVALID_OPERATION_HANDLE,
|
KeystoreErrorCode.INVALID_OPERATION_HANDLE,
|
||||||
"Operation already finalized.",
|
"Operation already finalized.",
|
||||||
@@ -300,10 +307,10 @@ class SoftwareOperation(
|
|||||||
try {
|
try {
|
||||||
primitive.updateAad(data)
|
primitive.updateAad(data)
|
||||||
} catch (e: ServiceSpecificException) {
|
} catch (e: ServiceSpecificException) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to updateAad.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to updateAad.", e)
|
||||||
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
|
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
|
||||||
}
|
}
|
||||||
@@ -314,35 +321,50 @@ class SoftwareOperation(
|
|||||||
try {
|
try {
|
||||||
return primitive.update(data)
|
return primitive.update(data)
|
||||||
} catch (e: ServiceSpecificException) {
|
} catch (e: ServiceSpecificException) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
||||||
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
|
throw mapToServiceSpecificException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
checkActive()
|
checkActive()
|
||||||
|
val startNs = if (latencyFloorMs > 0) System.nanoTime() else 0L
|
||||||
try {
|
try {
|
||||||
val result = primitive.finish(data, signature)
|
val result = primitive.finish(data, signature)
|
||||||
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
||||||
|
if (latencyFloorMs > 0) {
|
||||||
|
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
|
||||||
|
val delayMs = latencyFloorMs - elapsedMs
|
||||||
|
if (delayMs > 0) LockSupport.parkNanos(delayMs * 1_000_000)
|
||||||
|
}
|
||||||
onFinishCallback?.invoke()
|
onFinishCallback?.invoke()
|
||||||
return result
|
return result
|
||||||
} catch (e: ServiceSpecificException) {
|
} catch (e: ServiceSpecificException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
||||||
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
|
throw mapToServiceSpecificException(e)
|
||||||
} finally {
|
} finally {
|
||||||
finalized = true
|
isFinalized = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mapToServiceSpecificException(e: Exception): ServiceSpecificException = when (e) {
|
||||||
|
is ServiceSpecificException -> e
|
||||||
|
is SignatureException -> ServiceSpecificException(KeystoreErrorCode.VERIFICATION_FAILED, e.message)
|
||||||
|
is BadPaddingException -> ServiceSpecificException(KeystoreErrorCode.INVALID_ARGUMENT, e.message)
|
||||||
|
is IllegalBlockSizeException -> ServiceSpecificException(KeystoreErrorCode.INVALID_INPUT_LENGTH, e.message)
|
||||||
|
is java.security.InvalidKeyException -> ServiceSpecificException(KeystoreErrorCode.INCOMPATIBLE_KEY, e.message)
|
||||||
|
else -> ServiceSpecificException(KeystoreErrorCode.UNKNOWN_ERROR, e.message)
|
||||||
|
}
|
||||||
|
|
||||||
fun abort() {
|
fun abort() {
|
||||||
checkActive()
|
checkActive()
|
||||||
finalized = true
|
isFinalized = true
|
||||||
primitive.abort()
|
primitive.abort()
|
||||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,42 +76,38 @@ object AndroidDeviceUtils {
|
|||||||
SystemLogger.debug("Boot key and hash initialization complete.")
|
SystemLogger.debug("Boot key and hash initialization complete.")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic initializer for boot properties like the key and hash. It attempts to read from a
|
|
||||||
* system property first, then from a TEE attestation, and finally falls back to a random value
|
|
||||||
* if neither is available.
|
|
||||||
*
|
|
||||||
* @param propertyName The name of the system property (e.g., "ro.boot.vbmeta.digest").
|
|
||||||
* @param attestationValueProvider A function that supplies the value from a cached attestation.
|
|
||||||
* @param expectedSize The expected length of the byte array (e.g., 32 for a SHA-256 digest).
|
|
||||||
* @return The resulting byte array for the property.
|
|
||||||
*/
|
|
||||||
private fun initializeBootProperty(
|
private fun initializeBootProperty(
|
||||||
propertyName: String,
|
propertyName: String,
|
||||||
attestationValueProvider: () -> ByteArray?,
|
attestationValueProvider: () -> ByteArray?,
|
||||||
expectedSize: Int,
|
expectedSize: Int,
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
// 1. Attempt to get the value from the system property.
|
|
||||||
getProperty(propertyName, expectedSize)?.let {
|
getProperty(propertyName, expectedSize)?.let {
|
||||||
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
|
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
|
||||||
|
persistToFile(propertyName, it)
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fallback to the value from a cached TEE attestation.
|
|
||||||
try {
|
try {
|
||||||
attestationValueProvider()?.let {
|
attestationValueProvider()?.let {
|
||||||
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
||||||
setProperty(propertyName, it) // Persist for consistency
|
setProperty(propertyName, it)
|
||||||
|
persistToFile(propertyName, it)
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to get $propertyName from attestation.", e)
|
SystemLogger.error("Failed to get $propertyName from attestation.", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. As a final fallback, generate a random value.
|
readFromFile(propertyName, expectedSize)?.let {
|
||||||
|
SystemLogger.debug("Using $propertyName from persistent file: ${it.toHex()}")
|
||||||
|
setProperty(propertyName, it)
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
return generateRandomBytes(expectedSize).also {
|
return generateRandomBytes(expectedSize).also {
|
||||||
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
||||||
setProperty(propertyName, it)
|
setProperty(propertyName, it)
|
||||||
|
persistToFile(propertyName, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,10 +154,37 @@ object AndroidDeviceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generates a cryptographically random byte array of a specified length. */
|
|
||||||
private fun generateRandomBytes(size: Int): ByteArray =
|
private fun generateRandomBytes(size: Int): ByteArray =
|
||||||
ByteArray(size).also { ThreadLocalRandom.current().nextBytes(it) }
|
ByteArray(size).also { ThreadLocalRandom.current().nextBytes(it) }
|
||||||
|
|
||||||
|
private val PERSIST_DIR = File("/data/adb/tricky_store")
|
||||||
|
|
||||||
|
private fun fileForProperty(propertyName: String): File = when (propertyName) {
|
||||||
|
"ro.boot.vbmeta.digest" -> File(PERSIST_DIR, "boot_hash.bin")
|
||||||
|
"ro.boot.vbmeta.public_key_digest" -> File(PERSIST_DIR, "boot_key.bin")
|
||||||
|
else -> File(PERSIST_DIR, "${propertyName.replace('.', '_')}.bin")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun persistToFile(propertyName: String, bytes: ByteArray) {
|
||||||
|
try {
|
||||||
|
fileForProperty(propertyName).writeBytes(bytes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to persist $propertyName to file.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readFromFile(propertyName: String, expectedSize: Int): ByteArray? {
|
||||||
|
return try {
|
||||||
|
val file = fileForProperty(propertyName)
|
||||||
|
if (!file.exists()) return null
|
||||||
|
val bytes = file.readBytes()
|
||||||
|
if (bytes.size == expectedSize) bytes else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to read $propertyName from file.", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Patch Level Properties ---
|
// --- Patch Level Properties ---
|
||||||
|
|
||||||
fun getPatchLevel(uid: Int): Int {
|
fun getPatchLevel(uid: Int): Int {
|
||||||
@@ -240,11 +263,10 @@ object AndroidDeviceUtils {
|
|||||||
val resolvedValue = resolveDateKeywords(value)
|
val resolvedValue = resolveDateKeywords(value)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
// "device_default" indicates falling back to the system property.
|
|
||||||
resolvedValue.equals("device_default", ignoreCase = true) -> null
|
resolvedValue.equals("device_default", ignoreCase = true) -> null
|
||||||
// "no" indicates this value should not be reported.
|
|
||||||
resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT
|
resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT
|
||||||
// Otherwise, parse the resolved date string.
|
resolvedValue.equals("prop", ignoreCase = true) ->
|
||||||
|
parsePatchLevelValue(SystemProperties.get("ro.build.version.security_patch", ""), isLong)
|
||||||
else -> parsePatchLevelValue(resolvedValue, isLong)
|
else -> parsePatchLevelValue(resolvedValue, isLong)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
|
## TEESimulator-RS v5.1.1: Pre-Stash Restoration
|
||||||
|
|
||||||
|
Restores all custom hardening fixes that were lost during the PR #157 migration. These were working in pre-stash builds but never carried over to the post-stash codebase, causing user-reported regressions (boot hash instability, DuckDetector score regression, config crash on file deletion).
|
||||||
|
|
||||||
|
- **Boot hash persistence** restored: 4-step fallback (sysprop, TEE, file, random) with file writes at every step. Fixes "Boot: Unavailable" where boot hash randomized every reboot on devices without `ro.boot.vbmeta.digest`
|
||||||
|
- **Presence-based findBoolean** for KeyMint tags: boolean tags are presence-based per AIDL spec, `.boolValue` field isn't reliably populated across Android versions
|
||||||
|
- **noAuthRequired** defaults to true when not explicitly false, matching AOSP KeyMint behavior
|
||||||
|
- **callerNonce** tag now flows through to software-enforced attestation list
|
||||||
|
- **CTR block mode** restored in cipher algorithm mapping (was dropped in PR #157)
|
||||||
|
- **AEAD guard** on updateAad: non-GCM operations throw INVALID_TAG
|
||||||
|
- **Error code resolution** via lazy reflection with correct KeyMint AIDL fallback values
|
||||||
|
- **Latency floor** on SoftwareOperation.finish() for StrongBox timing simulation
|
||||||
|
- **FileObserver NPE** fixed: null-safe handling on config file DELETE events
|
||||||
|
- **system=prop** consistency: forces boot/vendor patch levels to derive from device props
|
||||||
|
- **StrongBox simulation** restored: capability checks (RSA<=2048, EC=P256), concurrent op limits (4 max), keygen latency floor (250ms), op latency floor (80ms)
|
||||||
|
- **Binder buffer guard**: MAX_ALIAS_LENGTH (256KB) rejects oversized aliases before processing
|
||||||
|
- **Key lifecycle tracking**: deletedSoftwareKeys set prevents ghost key responses after deletion
|
||||||
|
- **Per-UID operation limits**: 15 TEE, 4 StrongBox with LRU eviction
|
||||||
|
- **EC+DECRYPT rejection** in createOperation, matching AOSP unsupported purpose check
|
||||||
|
- **Attest key nspace update** aligned with upstream PR #169
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TEESimulator-RS v5.1: Interception Architecture Rewrite
|
## TEESimulator-RS v5.1: Interception Architecture Rewrite
|
||||||
|
|
||||||
Major release. 27 files changed, 2300 lines rewritten. The entire Kotlin interception layer has been rebuilt with a clean architecture, proper AIDL alignment, and significantly lower binder overhead.
|
Major release. 27 files changed, 2300 lines rewritten. The entire Kotlin interception layer has been rebuilt with a clean architecture, proper AIDL alignment, and significantly lower binder overhead.
|
||||||
|
|||||||
Reference in New Issue
Block a user