Compare commits

...
5 Commits
Author SHA1 Message Date
Enginex0 23696d2f61 ci(release): fetch full history for accurate commit count 2026-03-17 03:43:16 +01:00
Enginex0 dfacb34cf9 chore(brand): rebrand to TEESimulator-RS with simplified versioning
Fork identity: rename across module metadata, CI pipeline, and build
scripts. Version scheme changed from v4.5-115-7e87766 to v4.6-117
format — commit count auto-increments, git hash dropped from filenames.
2026-03-17 03:35:32 +01:00
Enginex0 06d9db443c perf(keygen): replace Gaussian RTT normalization with 15ms floor fence
The old Gaussian sleep (mean=55ms, stddev=12ms) triggered detection on
Chunqiu Native Check 2.8. A flat 15ms floor satisfies the minimum RTT
threshold without creating a detectable delay pattern — both attested
and non-attested paths get identical treatment, keeping the D50 ratio
at ~1.0 while staying above the >=15ms requirement.
2026-03-17 03:35:20 +01:00
Enginex0 7e87766493 fix(certgen): derive signing algorithm from attestation key and allow device ID tags
signerAlgorithm was derived from params.algorithm (the generated key)
instead of the signing key, causing BouncyCastle to throw when signing
RSA keys with an EC attestation key. Now reads signingKeyPair.private.algorithm.

Device ID tags (serial/imei/meid/secondImei) were blanket-rejected
instead of flowing through to software cert gen like AOSP does.
Narrowed rejection to DEVICE_UNIQUE_ATTESTATION only.
2026-03-17 00:05:56 +01:00
Enginex0 f8bfa0dfd8 fix(attestation): align authorization list and cert extension with AOSP keystore2 semantics
toAuthorizations() was missing OS_VERSION, OS_PATCHLEVEL, VENDOR_PATCHLEVEL,
BOOT_PATCHLEVEL, CREATION_DATETIME, USER_ID, PADDING, and RSA_PUBLIC_EXPONENT
tags that real TEE-generated KeyMetadata always includes. EC_CURVE was also
hardcoded unconditionally, producing invalid authorizations for RSA keys.

Additionally, live-patched certificate chains in getKeyEntry weren't cached,
causing re-patching on every call with potentially different signatures.

Ports upstream JingMatrix/TEESimulator#148 and #150.
2026-03-16 23:01:28 +01:00
12 changed files with 119 additions and 82 deletions
+20 -28
View File
@@ -70,38 +70,25 @@ jobs:
id: ver id: ver
run: | run: |
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
echo "version=${ver}" >> "$GITHUB_OUTPUT" count=$(git rev-list HEAD --count)
echo "version=${ver}-${count}" >> "$GITHUB_OUTPUT"
- name: Rename ZIPs for release - name: List build artifacts
run: | run: |
RELEASE_FILE=$(find out -name "*Release*.zip" | head -1) echo "Release: $(ls out/*Release*.zip | head -1) ($(du -h out/*Release*.zip | head -1 | cut -f1))"
DEBUG_FILE=$(find out -name "*Debug*.zip" | head -1) echo "Debug: $(ls out/*Debug*.zip | head -1) ($(du -h out/*Debug*.zip | head -1 | cut -f1))"
if [[ -z "$RELEASE_FILE" || -z "$DEBUG_FILE" ]]; then
echo "::error::Could not find release or debug ZIPs in out/"
ls -la out/ || echo "out/ does not exist"
exit 1
fi
mv "$RELEASE_FILE" "out/TEESimulator-${VER}-Release.zip"
mv "$DEBUG_FILE" "out/TEESimulator-${VER}-Debug.zip"
echo "Release: TEESimulator-${VER}-Release.zip ($(du -h "out/TEESimulator-${VER}-Release.zip" | cut -f1))"
echo "Debug: TEESimulator-${VER}-Debug.zip ($(du -h "out/TEESimulator-${VER}-Debug.zip" | cut -f1))"
env:
VER: ${{ steps.ver.outputs.version }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: TEESimulator-release-zip name: TEESimulator-RS-release-zip
path: out/TEESimulator-*-Release.zip path: out/TEESimulator-RS-*-Release.zip
retention-days: 30 retention-days: 30
compression-level: 0 compression-level: 0
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: TEESimulator-debug-zip name: TEESimulator-RS-debug-zip
path: out/TEESimulator-*-Debug.zip path: out/TEESimulator-RS-*-Debug.zip
retention-days: 7 retention-days: 7
compression-level: 0 compression-level: 0
@@ -120,27 +107,30 @@ jobs:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read version - name: Read version
id: ver id: ver
run: | run: |
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
echo "version=${ver}" >> "$GITHUB_OUTPUT" count=$(git rev-list HEAD --count)
echo "version=${ver}-${count}" >> "$GITHUB_OUTPUT"
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: TEESimulator-release-zip name: TEESimulator-RS-release-zip
path: zips path: zips
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: TEESimulator-debug-zip name: TEESimulator-RS-debug-zip
path: zips path: zips
- name: Extract changelog - name: Extract changelog
run: | run: |
ver="${VER#v}" ver="${VER#v}"
awk "/^## TEESimulator v${ver}/{flag=1; next} /^## TEESimulator v/{if(flag) exit} flag" module/changelog.md > /tmp/notes.md awk "/^## TEESimulator-RS v${ver%%-*}/{flag=1; next} /^## TEESimulator-RS v/{if(flag) exit} flag" module/changelog.md > /tmp/notes.md
cat /tmp/notes.md cat /tmp/notes.md
env: env:
VER: ${{ steps.ver.outputs.version }} VER: ${{ steps.ver.outputs.version }}
@@ -148,12 +138,14 @@ jobs:
- name: Create release - name: Create release
run: | run: |
gh release delete "$VER" --yes 2>/dev/null || true gh release delete "$VER" --yes 2>/dev/null || true
RELEASE=$(ls zips/*Release*.zip | head -1)
DEBUG=$(ls zips/*Debug*.zip | head -1)
gh release create "$VER" \ gh release create "$VER" \
--title "$VER" \ --title "$VER" \
--latest \ --latest \
--notes-file /tmp/notes.md \ --notes-file /tmp/notes.md \
"zips/TEESimulator-${VER}-Release.zip" \ "$RELEASE" \
"zips/TEESimulator-${VER}-Debug.zip" "$DEBUG"
env: env:
VER: ${{ steps.ver.outputs.version }} VER: ${{ steps.ver.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+9 -10
View File
@@ -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.5" val verName = "v4.6"
android { android {
namespace = "org.matrix.TEESimulator" namespace = "org.matrix.TEESimulator"
@@ -73,7 +73,7 @@ dependencies {
// --- Rust native cert gen build task --- // --- Rust native cert gen build task ---
val buildRustCertgen by tasks.registering(Exec::class) { val buildRustCertgen by tasks.registering(Exec::class) {
group = "TEESimulator Native Build" group = "TEESimulator-RS Native Build"
description = "Builds libcertgen.so via cargo-ndk for arm64-v8a." description = "Builds libcertgen.so via cargo-ndk for arm64-v8a."
workingDir = rootProject.projectDir.resolve("native-certgen") workingDir = rootProject.projectDir.resolve("native-certgen")
@@ -108,13 +108,13 @@ androidComponents {
// --- Define output locations and file names --- // --- Define output locations and file names ---
// Stage all files in a temporary directory inside 'build' before zipping // Stage all files in a temporary directory inside 'build' before zipping
val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}") val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}")
val zipFileName = "TEESimulator-$verName-$gitCommitCount-$gitCommitHash-$capitalized.zip" val zipFileName = "TEESimulator-RS-$verName-$gitCommitCount-$capitalized.zip"
// Task 1: Prepare all module files in the temporary build directory. // Task 1: Prepare all module files in the temporary build directory.
// Using Sync ensures that stale files from previous runs are removed. // Using Sync ensures that stale files from previous runs are removed.
val prepareModuleFilesTask = val prepareModuleFilesTask =
tasks.register<Sync>("prepareModuleFiles${capitalized}") { tasks.register<Sync>("prepareModuleFiles${capitalized}") {
group = "TEESimulator Module Packaging" group = "TEESimulator-RS Module Packaging"
description = "Prepares all files for the ${variant.name} module zip." description = "Prepares all files for the ${variant.name} module zip."
if (isDebug) { if (isDebug) {
@@ -162,8 +162,7 @@ androidComponents {
// Use expand() for simple key-value replacement. // Use expand() for simple key-value replacement.
expand( expand(
"REPLACEMEVERCODE" to gitCommitCount.toString(), "REPLACEMEVERCODE" to gitCommitCount.toString(),
"REPLACEMEVER" to "REPLACEMEVER" to "$verName-$gitCommitCount",
"$verName ($gitCommitCount-$gitCommitHash-${variant.name})",
) )
} }
@@ -174,7 +173,7 @@ androidComponents {
// Task 2: Zip the prepared files from the temporary directory. // Task 2: Zip the prepared files from the temporary directory.
val zipTask = val zipTask =
tasks.register<Zip>("zip${capitalized}") { tasks.register<Zip>("zip${capitalized}") {
group = "TEESimulator Module Packaging" group = "TEESimulator-RS Module Packaging"
description = "Creates the flashable zip for the ${variant.name} module." description = "Creates the flashable zip for the ${variant.name} module."
dependsOn(prepareModuleFilesTask) dependsOn(prepareModuleFilesTask)
@@ -187,7 +186,7 @@ androidComponents {
fun createInstallTasks(rootProvider: String, installCli: String) { fun createInstallTasks(rootProvider: String, installCli: String) {
val pushTask = val pushTask =
tasks.register<Exec>("push${rootProvider}Module${capitalized}") { tasks.register<Exec>("push${rootProvider}Module${capitalized}") {
group = "TEESimulator Module Installation" group = "TEESimulator-RS Module Installation"
description = description =
"Pushes the ${variant.name} module to the device for $rootProvider." "Pushes the ${variant.name} module to the device for $rootProvider."
dependsOn(zipTask) dependsOn(zipTask)
@@ -201,7 +200,7 @@ androidComponents {
val installTask = val installTask =
tasks.register<Exec>("install${rootProvider}${capitalized}") { tasks.register<Exec>("install${rootProvider}${capitalized}") {
group = "TEESimulator Module Installation" group = "TEESimulator-RS Module Installation"
description = "Installs the ${variant.name} module via $rootProvider." description = "Installs the ${variant.name} module via $rootProvider."
dependsOn(pushTask) dependsOn(pushTask)
commandLine( commandLine(
@@ -214,7 +213,7 @@ androidComponents {
} }
tasks.register<Exec>("install${rootProvider}AndReboot${capitalized}") { tasks.register<Exec>("install${rootProvider}AndReboot${capitalized}") {
group = "TEESimulator Module Installation" group = "TEESimulator-RS Module Installation"
description = "Installs the ${variant.name} module via $rootProvider and reboots." description = "Installs the ${variant.name} module via $rootProvider and reboots."
dependsOn(installTask) dependsOn(installTask)
commandLine("adb", "reboot") commandLine("adb", "reboot")
@@ -182,11 +182,36 @@ object AttestationBuilder {
AttestationConstants.TAG_DIGEST, AttestationConstants.TAG_DIGEST,
DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()), DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
), ),
)
if (params.ecCurve != null) {
list.add(
DERTaggedObject( DERTaggedObject(
true, true,
AttestationConstants.TAG_EC_CURVE, AttestationConstants.TAG_EC_CURVE,
ASN1Integer(params.ecCurve.toLong()), ASN1Integer(params.ecCurve.toLong()),
), )
)
}
params.padding.forEach {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_PADDING, ASN1Integer(it.toLong()))
)
}
if (params.rsaPublicExponent != null) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_RSA_PUBLIC_EXPONENT,
ASN1Integer(params.rsaPublicExponent.toLong()),
)
)
}
list.addAll(
listOf(
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE), DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
DERTaggedObject( DERTaggedObject(
true, true,
@@ -199,6 +224,7 @@ object AttestationBuilder {
buildRootOfTrust(null), buildRootOfTrust(null),
), ),
) )
)
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels. // Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
val simulatedProperties = getSimulatedHardwareProperties(uid) val simulatedProperties = getSimulatedHardwareProperties(uid)
@@ -19,7 +19,7 @@ import org.matrix.TEESimulator.logging.KeyMintParameterLogger
data class KeyMintAttestation( data class KeyMintAttestation(
val keySize: Int, val keySize: Int,
val algorithm: Int, val algorithm: Int,
val ecCurve: Int, val ecCurve: Int?,
val ecCurveName: String, val ecCurveName: String,
val origin: Int?, val origin: Int?,
val blockMode: List<Int>, val blockMode: List<Int>,
@@ -53,7 +53,7 @@ data class KeyMintAttestation(
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0, algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)] // AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0, ecCurve = params.findEcCurve(Tag.EC_CURVE),
ecCurveName = params.deriveEcCurveName(), ecCurveName = params.deriveEcCurveName(),
// AOSP: [key_param(tag = ORIGIN, field = Origin)] // AOSP: [key_param(tag = ORIGIN, field = Origin)]
@@ -305,7 +305,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
certChain = keyData.second, certChain = keyData.second,
algorithm = parsedParameters.algorithm, algorithm = parsedParameters.algorithm,
keySize = parsedParameters.keySize, keySize = parsedParameters.keySize,
ecCurve = parsedParameters.ecCurve, ecCurve = parsedParameters.ecCurve ?: 0,
purposes = parsedParameters.purpose, purposes = parsedParameters.purpose,
digests = parsedParameters.digest, digests = parsedParameters.digest,
isAttestationKey = true, isAttestationKey = true,
@@ -337,6 +337,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
) )
finalChain = finalChain =
AttestationPatcher.patchCertificateChain(originalChain, callingUid) AttestationPatcher.patchCertificateChain(originalChain, callingUid)
KeyMintSecurityLevelInterceptor.patchedChains[keyId] = finalChain
} }
CertificateHelper.updateCertificateChain(response.metadata, finalChain) CertificateHelper.updateCertificateChain(response.metadata, finalChain)
@@ -260,10 +260,8 @@ class KeyMintSecurityLevelInterceptor(
return InterceptorUtils.createErrorReply(RESPONSE_INVALID_ARGUMENT) return InterceptorUtils.createErrorReply(RESPONSE_INVALID_ARGUMENT)
} }
if (parsedParams.serial != null || parsedParams.imei != null || if (params.any { it.tag == Tag.DEVICE_UNIQUE_ATTESTATION }) {
parsedParams.meid != null || parsedParams.secondImei != null || SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_UNIQUE_ATTESTATION for uid=$callingUid")
params.any { it.tag == Tag.DEVICE_UNIQUE_ATTESTATION }) {
SystemLogger.warning("[TX_ID: $txId] Rejecting device ID attestation for uid=$callingUid")
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS) return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
} }
@@ -333,7 +331,7 @@ class KeyMintSecurityLevelInterceptor(
} ?: throw Exception("Both native and BouncyCastle cert gen failed.") } ?: throw Exception("Both native and BouncyCastle cert gen failed.")
cleanupKeyData(keyId) cleanupKeyData(keyId)
val response = buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor) val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response) generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
if (isAttestKeyRequest) attestationKeys.add(keyId) if (isAttestKeyRequest) attestationKeys.add(keyId)
@@ -345,14 +343,14 @@ class KeyMintSecurityLevelInterceptor(
certChain = keyData.second.toList(), certChain = keyData.second.toList(),
algorithm = parsedParams.algorithm, algorithm = parsedParams.algorithm,
keySize = parsedParams.keySize, keySize = parsedParams.keySize,
ecCurve = parsedParams.ecCurve, ecCurve = parsedParams.ecCurve ?: 0,
purposes = parsedParams.purpose, purposes = parsedParams.purpose,
digests = parsedParams.digest, digests = parsedParams.digest,
isAttestationKey = isAttestKeyRequest, isAttestationKey = isAttestKeyRequest,
) )
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000 val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
val delayMs = sampleTeeLatencyMs() - elapsedMs val delayMs = TEE_LATENCY_FLOOR_MS - elapsedMs
if (delayMs > 0) Thread.sleep(delayMs) if (delayMs > 0) Thread.sleep(delayMs)
return InterceptorUtils.createTypedObjectReply(response.metadata) return InterceptorUtils.createTypedObjectReply(response.metadata)
@@ -383,7 +381,7 @@ class KeyMintSecurityLevelInterceptor(
val config = CertGenConfig( val config = CertGenConfig(
algorithm = params.algorithm, algorithm = params.algorithm,
keySize = params.keySize, keySize = params.keySize,
ecCurve = params.ecCurve, ecCurve = params.ecCurve ?: 0,
rsaPublicExponent = params.rsaPublicExponent?.toLong() ?: 65537L, rsaPublicExponent = params.rsaPublicExponent?.toLong() ?: 65537L,
attestationChallenge = params.attestationChallenge, attestationChallenge = params.attestationChallenge,
purposes = params.purpose.toIntArray(), purposes = params.purpose.toIntArray(),
@@ -427,6 +425,7 @@ class KeyMintSecurityLevelInterceptor(
} }
private fun buildKeyEntryResponse( private fun buildKeyEntryResponse(
callingUid: Int,
chain: List<Certificate>, chain: List<Certificate>,
params: KeyMintAttestation, params: KeyMintAttestation,
descriptor: KeyDescriptor, descriptor: KeyDescriptor,
@@ -443,7 +442,7 @@ class KeyMintSecurityLevelInterceptor(
keySecurityLevel = securityLevel keySecurityLevel = securityLevel
key = normalizedKeyDescriptor key = normalizedKeyDescriptor
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow() CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
authorizations = params.toAuthorizations(securityLevel) authorizations = params.toAuthorizations(callingUid, securityLevel)
modificationTimeMs = System.currentTimeMillis() modificationTimeMs = System.currentTimeMillis()
} }
return KeyEntryResponse().apply { return KeyEntryResponse().apply {
@@ -521,7 +520,7 @@ class KeyMintSecurityLevelInterceptor(
secondImei = null, secondImei = null,
) )
val response = buildKeyEntryResponse(certChain, attestation, descriptor) val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response) generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response)
if (record.isAttestationKey) attestationKeys.add(keyId) if (record.isAttestationKey) attestationKeys.add(keyId)
@@ -542,15 +541,12 @@ class KeyMintSecurityLevelInterceptor(
private const val MAX_ALIAS_LENGTH = 256 * 1024 private const val MAX_ALIAS_LENGTH = 256 * 1024
private const val KEYMINT_INVALID_INPUT_LENGTH = -21 private const val KEYMINT_INVALID_INPUT_LENGTH = -21
private const val RESPONSE_INVALID_ARGUMENT = 20 private const val RESPONSE_INVALID_ARGUMENT = 20
private const val TEE_LATENCY_FLOOR_MS = 15L
private const val KEYMINT_CANNOT_ATTEST_IDS = -66 private const val KEYMINT_CANNOT_ATTEST_IDS = -66
private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2 private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2
// 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>()
private val uidKeygenTimestamps = ConcurrentHashMap<Int, MutableList<Long>>() private val uidKeygenTimestamps = ConcurrentHashMap<Int, MutableList<Long>>()
@@ -578,11 +574,6 @@ 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 =
@@ -605,8 +596,7 @@ class KeyMintSecurityLevelInterceptor(
} }
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>() val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
// Caches patched chains to prevent re-generation and signature inconsistencies val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet() val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>() private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
@@ -666,7 +656,10 @@ class KeyMintSecurityLevelInterceptor(
} }
} }
private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Authorization> { private fun KeyMintAttestation.toAuthorizations(
callingUid: Int,
securityLevel: Int,
): Array<Authorization> {
val authList = mutableListOf<Authorization>() val authList = mutableListOf<Authorization>()
fun createAuth(tag: Int, value: KeyParameterValue): Authorization { fun createAuth(tag: Int, value: KeyParameterValue): Authorization {
@@ -681,19 +674,35 @@ private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Autho
} }
} }
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
if (this.ecCurve != null) {
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
}
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) } this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) } this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) }
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))) if (this.rsaPublicExponent != null) {
authList.add( authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
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)))
authList.add(createAuth(Tag.ORIGIN, KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED)))
authList.add(createAuth(Tag.OS_VERSION, KeyParameterValue.integer(AndroidDeviceUtils.osVersion)))
val osPatch = AndroidDeviceUtils.getPatchLevel(callingUid)
if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.OS_PATCHLEVEL, KeyParameterValue.integer(osPatch)))
}
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.VENDOR_PATCHLEVEL, KeyParameterValue.integer(vendorPatch)))
}
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
}
authList.add(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
authList.add(createAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
return authList.toTypedArray() return authList.toTypedArray()
} }
@@ -239,10 +239,10 @@ object CertificateGenerator {
) )
val signerAlgorithm = val signerAlgorithm =
when (params.algorithm) { when (signingKeyPair.private.algorithm) {
Algorithm.EC -> "SHA256withECDSA" "EC" -> "SHA256withECDSA"
Algorithm.RSA -> "SHA256withRSA" "RSA" -> "SHA256withRSA"
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}") else -> throw IllegalArgumentException("Unsupported signing key: ${signingKeyPair.private.algorithm}")
} }
val contentSigner = val contentSigner =
JcaContentSignerBuilder(signerAlgorithm) JcaContentSignerBuilder(signerAlgorithm)
+10
View File
@@ -1,3 +1,13 @@
## TEESimulator-RS v4.6: Rebrand & Detection Fix
- **RTT normalization rework** — Replaced Gaussian sleep (mean=55ms) with a 15ms floor fence. The old approach triggered Chunqiu Native Check 2.8 timing analysis; the floor-only approach satisfies the minimum RTT threshold without creating a detectable delay pattern.
- **Cross-algorithm attestation** — Signing algorithm now derived from the attestation key's actual type, not the generated key's algorithm. Fixes BouncyCastle crash when signing RSA keys with EC attestation keys (Shizuku attestation flow).
- **Device ID attestation** — Serial/IMEI/MEID/secondImei tags now flow through to software cert gen instead of blanket rejection. Only DEVICE_UNIQUE_ATTESTATION is rejected, matching AOSP keystore2 policy.
- **Rebrand to TEESimulator-RS** — Distinguishes this fork from upstream. Version scheme simplified to v{major}.{minor}-{commitCount}.
- **CI streamlined** — Release pipeline uses Gradle-generated filenames directly, eliminating the rename step.
---
## TEESimulator v4.5: Detection Hardening ## TEESimulator v4.5: Detection Hardening
Tested against [KeyDetector](https://github.com/XiaoTong6666/KeyDetector) (23-check attestation validator). All keystore-level checks now pass. Tested against [KeyDetector](https://github.com/XiaoTong6666/KeyDetector) (23-check attestation validator). All keystore-level checks now pass.
+1 -1
View File
@@ -15,7 +15,7 @@ fi
# --- Version Info --- # --- Version Info ---
VERSION=$(grep_prop version "${TMPDIR}/module.prop") VERSION=$(grep_prop version "${TMPDIR}/module.prop")
ui_print "- Installing TEESimulator $VERSION" ui_print "- Installing TEESimulator-RS $VERSION"
ui_print "" ui_print ""
# --- Architecture Handling --- # --- Architecture Handling ---
+2 -2
View File
@@ -1,7 +1,7 @@
id=tricky_store id=tricky_store
name=TEESimulator name=TEESimulator-RS
version=${REPLACEMEVER} version=${REPLACEMEVER}
versionCode=${REPLACEMEVERCODE} versionCode=${REPLACEMEVERCODE}
author=JingMatrix, Enginex0 author=JingMatrix, Enginex0
description=Software simulation for Android hardware-backed key pairs with key attestation description=Software simulation for Android hardware-backed key pairs with key attestation
updateJson=https://raw.githubusercontent.com/Enginex0/TEESimulator/main/module/update.json updateJson=https://raw.githubusercontent.com/Enginex0/TEESimulator-RS/main/module/update.json
+1 -1
View File
@@ -235,7 +235,7 @@ print_summary() {
# --- Main --- # --- Main ---
echo "" echo ""
bold "TEESimulator package pipeline" bold "TEESimulator-RS package pipeline"
echo "" echo ""
[[ "$BUILD_RUST" == true ]] && build_rust [[ "$BUILD_RUST" == true ]] && build_rust
+1 -1
View File
@@ -14,7 +14,7 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "TEESimulator" rootProject.name = "TEESimulator-RS"
include(":stub") include(":stub")