Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ff59e0aa | ||
|
|
8bdf0d59fa | ||
|
|
6ab09f4889 | ||
|
|
f4559bcd19 | ||
|
|
3b5043a1bb | ||
|
|
8001a8678a |
@@ -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 = "v4.3"
|
val verName = "v4.5"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
|
|||||||
@@ -89,5 +89,5 @@ object AttestationConstants {
|
|||||||
|
|
||||||
// --- Other Constants ---
|
// --- Other Constants ---
|
||||||
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
|
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
|
||||||
const val CHALLENGE_LENGTH_LIMIT = 128 // kMaximumAttestationChallengeLength
|
const val CHALLENGE_LENGTH_LIMIT = 128
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ object InterceptorUtils {
|
|||||||
val parcel = Parcel.obtain().apply {
|
val parcel = Parcel.obtain().apply {
|
||||||
writeInt(EX_SERVICE_SPECIFIC)
|
writeInt(EX_SERVICE_SPECIFIC)
|
||||||
writeString(null)
|
writeString(null)
|
||||||
|
writeInt(0) // empty remote stack trace header (AOSP Status.cpp:196)
|
||||||
writeInt(errorCode)
|
writeInt(errorCode)
|
||||||
}
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
@@ -119,6 +120,8 @@ object InterceptorUtils {
|
|||||||
|
|
||||||
/** Checks if a reply parcel contains an exception without consuming it. */
|
/** Checks if a reply parcel contains an exception without consuming it. */
|
||||||
fun hasException(reply: Parcel): Boolean {
|
fun hasException(reply: Parcel): Boolean {
|
||||||
return runCatching { reply.readException() }.exceptionOrNull() != null
|
val exception = runCatching { reply.readException() }.exceptionOrNull()
|
||||||
|
if (exception != null) reply.setDataPosition(0)
|
||||||
|
return exception != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-4
@@ -10,6 +10,7 @@ import android.system.keystore2.KeyDescriptor
|
|||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
@@ -54,6 +55,9 @@ 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] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val RESPONSE_KEY_NOT_FOUND = 7
|
||||||
|
private val deletedSoftwareKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
|
|
||||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||||
override val processName = "keystore2"
|
override val processName = "keystore2"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
||||||
@@ -156,8 +160,10 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||||
|
|
||||||
if (code == DELETE_KEY_TRANSACTION) {
|
if (code == DELETE_KEY_TRANSACTION) {
|
||||||
if (KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null) {
|
val wasSoftwareKey = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null
|
||||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
||||||
|
if (wasSoftwareKey) {
|
||||||
|
deletedSoftwareKeys.add(keyId)
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response."
|
"[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response."
|
||||||
)
|
)
|
||||||
@@ -166,9 +172,14 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
val response =
|
val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
||||||
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
if (response == null) {
|
||||||
?: return TransactionResult.Continue
|
if (deletedSoftwareKeys.remove(keyId)) {
|
||||||
|
SystemLogger.info("[TX_ID: $txId] Returning KEY_NOT_FOUND for deleted key ${descriptor.alias}")
|
||||||
|
return InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
|
||||||
|
}
|
||||||
|
return TransactionResult.Continue
|
||||||
|
}
|
||||||
|
|
||||||
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
|
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
|
||||||
SystemLogger.info("${descriptor.alias} was an attestation key")
|
SystemLogger.info("${descriptor.alias} was an attestation key")
|
||||||
|
|||||||
+1
-1
@@ -129,7 +129,7 @@ object ListEntriesHandler {
|
|||||||
startPastAlias: String?,
|
startPastAlias: String?,
|
||||||
): List<KeyDescriptor> {
|
): List<KeyDescriptor> {
|
||||||
return KeyMintSecurityLevelInterceptor.generatedKeys.keys
|
return KeyMintSecurityLevelInterceptor.generatedKeys.keys
|
||||||
.filter { it.uid == uid && (startPastAlias == null || it.alias < startPastAlias) }
|
.filter { it.uid == uid && (startPastAlias == null || it.alias > startPastAlias) }
|
||||||
.map { keyId ->
|
.map { keyId ->
|
||||||
KeyDescriptor().apply {
|
KeyDescriptor().apply {
|
||||||
this.domain = Domain.APP
|
this.domain = Domain.APP
|
||||||
|
|||||||
+29
-1
@@ -3,6 +3,7 @@ package org.matrix.TEESimulator.interception.keystore.shim
|
|||||||
import android.hardware.security.keymint.Algorithm
|
import android.hardware.security.keymint.Algorithm
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
import android.hardware.security.keymint.KeyParameterValue
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
|
import android.hardware.security.keymint.KeyOrigin
|
||||||
import android.hardware.security.keymint.Tag
|
import android.hardware.security.keymint.Tag
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
@@ -316,6 +317,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
keyId: KeyIdentifier,
|
keyId: KeyIdentifier,
|
||||||
isAttestKeyRequest: Boolean,
|
isAttestKeyRequest: Boolean,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
|
val startNs = System.nanoTime()
|
||||||
keyDescriptor.nspace = secureRandom.nextLong()
|
keyDescriptor.nspace = secureRandom.nextLong()
|
||||||
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
|
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
|
||||||
|
|
||||||
@@ -349,6 +351,10 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
isAttestationKey = isAttestKeyRequest,
|
isAttestationKey = isAttestKeyRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
|
||||||
|
val delayMs = sampleTeeLatencyMs() - elapsedMs
|
||||||
|
if (delayMs > 0) Thread.sleep(delayMs)
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,12 +431,20 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
descriptor: KeyDescriptor,
|
descriptor: KeyDescriptor,
|
||||||
): KeyEntryResponse {
|
): KeyEntryResponse {
|
||||||
|
val normalizedKeyDescriptor =
|
||||||
|
KeyDescriptor().apply {
|
||||||
|
domain = Domain.KEY_ID
|
||||||
|
nspace = descriptor.nspace
|
||||||
|
alias = null
|
||||||
|
blob = null
|
||||||
|
}
|
||||||
val metadata =
|
val metadata =
|
||||||
KeyMetadata().apply {
|
KeyMetadata().apply {
|
||||||
keySecurityLevel = securityLevel
|
keySecurityLevel = securityLevel
|
||||||
key = descriptor
|
key = normalizedKeyDescriptor
|
||||||
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
|
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
|
||||||
authorizations = params.toAuthorizations(securityLevel)
|
authorizations = params.toAuthorizations(securityLevel)
|
||||||
|
modificationTimeMs = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
return KeyEntryResponse().apply {
|
return KeyEntryResponse().apply {
|
||||||
this.metadata = metadata
|
this.metadata = metadata
|
||||||
@@ -533,6 +547,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
// Sliding window: max hardware keygen permits per UID within the burst window
|
// Sliding window: max hardware keygen permits per UID within the burst window
|
||||||
private const val MAX_HW_KEYGEN_PER_WINDOW = 2
|
private const val MAX_HW_KEYGEN_PER_WINDOW = 2
|
||||||
private const val BURST_WINDOW_MS = 30_000L
|
private const val BURST_WINDOW_MS = 30_000L
|
||||||
|
private const val TEE_LATENCY_MEAN_MS = 55.0
|
||||||
|
private const val TEE_LATENCY_STDDEV_MS = 12.0
|
||||||
|
private const val TEE_LATENCY_FLOOR_MS = 15L
|
||||||
|
|
||||||
private val uidHardwareKeygenCount = ConcurrentHashMap<Int, AtomicInteger>()
|
private val uidHardwareKeygenCount = ConcurrentHashMap<Int, AtomicInteger>()
|
||||||
private val hardwareKeygenTxIds = ConcurrentHashMap.newKeySet<Long>()
|
private val hardwareKeygenTxIds = ConcurrentHashMap.newKeySet<Long>()
|
||||||
@@ -561,6 +578,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sampleTeeLatencyMs(): Long {
|
||||||
|
val sample = TEE_LATENCY_MEAN_MS + secureRandom.nextGaussian() * TEE_LATENCY_STDDEV_MS
|
||||||
|
return sample.toLong().coerceAtLeast(TEE_LATENCY_FLOOR_MS)
|
||||||
|
}
|
||||||
|
|
||||||
private val GENERATE_KEY_TRANSACTION =
|
private val GENERATE_KEY_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||||
private val IMPORT_KEY_TRANSACTION =
|
private val IMPORT_KEY_TRANSACTION =
|
||||||
@@ -665,6 +687,12 @@ private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Autho
|
|||||||
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
|
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
|
||||||
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
||||||
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
||||||
|
authList.add(
|
||||||
|
createAuth(
|
||||||
|
Tag.ORIGIN,
|
||||||
|
KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED),
|
||||||
|
)
|
||||||
|
)
|
||||||
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
|
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||||
|
|
||||||
return authList.toTypedArray()
|
return authList.toTypedArray()
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
## TEESimulator v4.5: Detection Hardening
|
||||||
|
|
||||||
|
Tested against [KeyDetector](https://github.com/XiaoTong6666/KeyDetector) (23-check attestation validator). All keystore-level checks now pass.
|
||||||
|
|
||||||
|
- **Key deletion consistency** — After deleting a software-generated key, `getKeyEntry` now correctly returns `KEY_NOT_FOUND` instead of falling through to a stale live-patch fallback. Fixes binder consistency checks that detect ghost key responses.
|
||||||
|
- **generateKey timing normalization** — Software key generation RTT now matches real TEE latency profile (Gaussian distribution, mean=55ms, floor=15ms). Previously completed in ~4ms, which is an immediate timing side-channel.
|
||||||
|
- **Delete cleanup scope** — `deleteKey` now clears all cached state (patched chains, attestation keys) regardless of whether the key was software or hardware-generated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TEESimulator v4.4: AOSP Conformance
|
||||||
|
|
||||||
|
- **Binder error reply format** — Aligned EX_SERVICE_SPECIFIC wire layout with AOSP Status.cpp, including the remote stack trace header field.
|
||||||
|
- **Key enumeration** — Corrected list_past_alias pagination order to match AOSP database.rs semantics.
|
||||||
|
- **KeyMetadata fields** — Generated key responses now include modificationTimeMs, Tag.ORIGIN, and normalized KeyDescriptor fields per AOSP Keystore2.
|
||||||
|
- **Parcel handling** — hasException() preserves reply position for downstream consumers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TEESimulator v4.3: Performance & Reliability
|
## TEESimulator v4.3: Performance & Reliability
|
||||||
|
|
||||||
- **Debug log gating** — `SystemLogger.debug()` now skipped entirely in release builds, eliminating unnecessary logcat syscalls on every intercepted transaction.
|
- **Debug log gating** — `SystemLogger.debug()` now skipped entirely in release builds, eliminating unnecessary logcat syscalls on every intercepted transaction.
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "v4.3",
|
"version": "v4.5",
|
||||||
"versionCode": 107,
|
"versionCode": 111,
|
||||||
"zipUrl": "https://github.com/Enginex0/TEESimulator/releases/download/v4.3/TEESimulator-v4.3-Release.zip",
|
"zipUrl": "https://github.com/Enginex0/TEESimulator/releases/download/v4.5/TEESimulator-v4.5-Release.zip",
|
||||||
"changelog": "https://raw.githubusercontent.com/Enginex0/TEESimulator/main/module/changelog.md"
|
"changelog": "https://raw.githubusercontent.com/Enginex0/TEESimulator/main/module/changelog.md"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user