Compare commits
13
Commits
v6.0.0-224
...
v6.0.0-235
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
134d5111ad | ||
|
|
4c801f2089 | ||
|
|
afc5caeb1b | ||
|
|
0e9ea10b50 | ||
|
|
6ae5ea391c | ||
|
|
f554b36416 | ||
|
|
684542f4b1 | ||
|
|
240728f98d | ||
|
|
95b8c27a9f | ||
|
|
66a8c7fbf8 | ||
|
|
36c93decc6 | ||
|
|
55e39c7f01 | ||
|
|
44816c1a8d |
@@ -116,12 +116,21 @@ object InterceptorUtils {
|
|||||||
fun <T : Parcelable?> createTypedObjectReply(
|
fun <T : Parcelable?> createTypedObjectReply(
|
||||||
obj: T,
|
obj: T,
|
||||||
flags: Int = 0,
|
flags: Int = 0,
|
||||||
|
diagnosticTag: String? = null,
|
||||||
): BinderInterceptor.TransactionResult.OverrideReply {
|
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||||
val parcel =
|
val parcel =
|
||||||
Parcel.obtain().apply {
|
Parcel.obtain().apply {
|
||||||
writeNoException()
|
writeNoException()
|
||||||
writeTypedObject(obj, flags)
|
writeTypedObject(obj, flags)
|
||||||
}
|
}
|
||||||
|
if (diagnosticTag != null && SystemLogger.isDebugBuild) {
|
||||||
|
val savedPos = parcel.dataPosition()
|
||||||
|
val wire = parcel.marshall()
|
||||||
|
parcel.setDataPosition(savedPos)
|
||||||
|
val path = "/data/local/tmp/teesim-$diagnosticTag-${System.nanoTime()}.bin"
|
||||||
|
runCatching { java.io.File(path).writeBytes(wire) }
|
||||||
|
SystemLogger.debug("[$diagnosticTag] reply len=${wire.size} path=$path")
|
||||||
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-114
@@ -21,12 +21,10 @@ import java.security.cert.Certificate
|
|||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque
|
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.atomic.AtomicInteger
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
import java.util.concurrent.locks.LockSupport
|
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
|
||||||
@@ -60,10 +58,6 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val keyParams: KeyMintAttestation? = null,
|
val keyParams: KeyMintAttestation? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
// null = undecided, true = TEE works (use PATCH), false = TEE broken (use GENERATE)
|
|
||||||
// Instance field so TRUSTED_ENVIRONMENT and STRONGBOX decide independently
|
|
||||||
val teePathDecision = AtomicReference<Boolean?>(null)
|
|
||||||
|
|
||||||
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||||
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||||
|
|
||||||
@@ -76,18 +70,16 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
val shouldSkip = ConfigurationManager.shouldSkipUid(callingUid)
|
|
||||||
|
|
||||||
when (code) {
|
when (code) {
|
||||||
GENERATE_KEY_TRANSACTION -> {
|
GENERATE_KEY_TRANSACTION -> {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
|
|
||||||
if (!shouldSkip) return handleGenerateKey(txId, callingUid, callingPid, data)
|
return handleGenerateKey(txId, callingUid, callingPid, data)
|
||||||
}
|
}
|
||||||
CREATE_OPERATION_TRANSACTION -> {
|
CREATE_OPERATION_TRANSACTION -> {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
|
|
||||||
if (!shouldSkip) return handleCreateOperation(txId, callingUid, data)
|
return handleCreateOperation(txId, callingUid, data)
|
||||||
}
|
}
|
||||||
IMPORT_KEY_TRANSACTION -> {
|
IMPORT_KEY_TRANSACTION -> {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
@@ -424,6 +416,14 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 (SystemLogger.isDebugBuild) {
|
||||||
|
val savedPos = data.dataPosition()
|
||||||
|
val req = data.marshall()
|
||||||
|
data.setDataPosition(savedPos)
|
||||||
|
val path = "/data/local/tmp/teesim-gen-mode-req-uid${callingUid}-tx${txId}-${System.nanoTime()}.bin"
|
||||||
|
runCatching { java.io.File(path).writeBytes(req) }
|
||||||
|
SystemLogger.debug("[gen-mode-req] uid=$callingUid txId=$txId len=${req.size} path=$path")
|
||||||
|
}
|
||||||
val oversized = data.dataSize() > MAX_ALIAS_LENGTH
|
val oversized = data.dataSize() > MAX_ALIAS_LENGTH
|
||||||
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
@@ -436,6 +436,12 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
val parsedParams = KeyMintAttestation(params)
|
val parsedParams = KeyMintAttestation(params)
|
||||||
|
val isAttestKeyRequest = parsedParams.isAttestKey()
|
||||||
|
|
||||||
|
if (ConfigurationManager.shouldSkipUid(callingUid)
|
||||||
|
&& attestationKey == null && !isAttestKeyRequest) {
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
|
||||||
SystemLogger.trace { "[TRACE-$txId] generateKey alias=${keyDescriptor.alias} algo=${parsedParams.algorithm} challenge=${parsedParams.attestationChallenge?.size ?: "null"} serial=${parsedParams.serial != null} imei=${parsedParams.imei != null} noAuth=${parsedParams.noAuthRequired} purposes=${parsedParams.purpose}" }
|
SystemLogger.trace { "[TRACE-$txId] generateKey alias=${keyDescriptor.alias} algo=${parsedParams.algorithm} challenge=${parsedParams.attestationChallenge?.size ?: "null"} serial=${parsedParams.serial != null} imei=${parsedParams.imei != null} noAuth=${parsedParams.noAuthRequired} purposes=${parsedParams.purpose}" }
|
||||||
if (SystemLogger.isDebugBuild) params.forEach { p ->
|
if (SystemLogger.isDebugBuild) params.forEach { p ->
|
||||||
@@ -496,26 +502,17 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
val isAttestKeyRequest = parsedParams.isAttestKey()
|
|
||||||
|
|
||||||
val forceGenerate =
|
val forceGenerate =
|
||||||
oversized ||
|
oversized ||
|
||||||
ConfigurationManager.shouldGenerate(callingUid) ||
|
ConfigurationManager.shouldGenerate(callingUid) ||
|
||||||
(ConfigurationManager.shouldPatch(callingUid) && isAttestKeyRequest) ||
|
isAttestKeyRequest ||
|
||||||
(attestationKey != null &&
|
attestationKey != null
|
||||||
(attestationKey.alias?.let { isAttestationKey(KeyIdentifier(callingUid, it)) }
|
|
||||||
?: attestationKeys.any { kid -> kid.uid == callingUid && generatedKeys[kid]?.nspace == attestationKey.nspace }))
|
|
||||||
|
|
||||||
val isAuto = ConfigurationManager.isAutoMode(callingUid)
|
SystemLogger.trace { "[TRACE-$txId] dispatch: forceGen=$forceGenerate hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest" }
|
||||||
|
|
||||||
if (isAuto) SystemLogger.debug("AUTO dispatch: teePathDecision=${teePathDecision.get()} for ${keyDescriptor.alias}")
|
|
||||||
|
|
||||||
SystemLogger.trace { "[TRACE-$txId] dispatch: forceGen=$forceGenerate isAuto=$isAuto teePath=${teePathDecision.get()} hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest" }
|
|
||||||
|
|
||||||
when {
|
when {
|
||||||
forceGenerate -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
|
forceGenerate -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
|
||||||
isAuto && teePathDecision.get() == null -> raceTeePatch(callingUid, keyDescriptor, attestationKey, params, parsedParams, keyId, isAttestKeyRequest)
|
|
||||||
isAuto && teePathDecision.get() == false -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
|
|
||||||
parsedParams.attestationChallenge != null -> TransactionResult.Continue
|
parsedParams.attestationChallenge != null -> TransactionResult.Continue
|
||||||
else -> {
|
else -> {
|
||||||
cleanupKeyData(keyId)
|
cleanupKeyData(keyId)
|
||||||
@@ -547,11 +544,17 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
parsedParams.algorithm != Algorithm.RSA
|
parsedParams.algorithm != Algorithm.RSA
|
||||||
|
|
||||||
if (isSymmetric) {
|
if (isSymmetric) {
|
||||||
|
if (attestationKey != null) {
|
||||||
|
throw android.os.ServiceSpecificException(
|
||||||
|
KEYMINT_INVALID_ARGUMENT,
|
||||||
|
"ATTEST_KEY tag is not supported for symmetric algorithms (algo=${parsedParams.algorithm})",
|
||||||
|
)
|
||||||
|
}
|
||||||
val algoName = when (parsedParams.algorithm) {
|
val algoName = when (parsedParams.algorithm) {
|
||||||
Algorithm.AES -> "AES"
|
Algorithm.AES -> "AES"
|
||||||
Algorithm.HMAC -> "HmacSHA256"
|
Algorithm.HMAC -> "HmacSHA256"
|
||||||
else -> throw android.os.ServiceSpecificException(
|
else -> throw android.os.ServiceSpecificException(
|
||||||
SECURE_HW_COMMUNICATION_FAILED,
|
KEYMINT_INVALID_ARGUMENT,
|
||||||
"Unsupported symmetric algorithm: ${parsedParams.algorithm}",
|
"Unsupported symmetric algorithm: ${parsedParams.algorithm}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -620,7 +623,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
TeeLatencySimulator.simulateGenerateKeyDelay(parsedParams.algorithm, System.nanoTime() - genStartNanos)
|
TeeLatencySimulator.simulateGenerateKeyDelay(parsedParams.algorithm, System.nanoTime() - genStartNanos)
|
||||||
}
|
}
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(metadata)
|
return InterceptorUtils.createTypedObjectReply(metadata, diagnosticTag = "gen-mode-sym")
|
||||||
}
|
}
|
||||||
|
|
||||||
val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
|
val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
|
||||||
@@ -693,94 +696,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
TeeLatencySimulator.simulateGenerateKeyDelay(parsedParams.algorithm, System.nanoTime() - genStartNanos)
|
TeeLatencySimulator.simulateGenerateKeyDelay(parsedParams.algorithm, System.nanoTime() - genStartNanos)
|
||||||
}
|
}
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
return InterceptorUtils.createTypedObjectReply(response.metadata, diagnosticTag = "gen-mode-asym")
|
||||||
}
|
|
||||||
|
|
||||||
private fun raceTeePatch(
|
|
||||||
callingUid: Int,
|
|
||||||
keyDescriptor: KeyDescriptor,
|
|
||||||
attestationKey: KeyDescriptor?,
|
|
||||||
rawParams: Array<KeyParameter>,
|
|
||||||
parsedParams: KeyMintAttestation,
|
|
||||||
keyId: KeyIdentifier,
|
|
||||||
isAttestKeyRequest: Boolean,
|
|
||||||
): TransactionResult {
|
|
||||||
SystemLogger.info("AUTO: racing TEE vs software for ${keyDescriptor.alias}")
|
|
||||||
|
|
||||||
val teeDescriptor = KeyDescriptor().apply {
|
|
||||||
domain = keyDescriptor.domain
|
|
||||||
nspace = keyDescriptor.nspace
|
|
||||||
alias = keyDescriptor.alias
|
|
||||||
blob = keyDescriptor.blob
|
|
||||||
}
|
|
||||||
val teeAttestKey = attestationKey?.let {
|
|
||||||
KeyDescriptor().apply {
|
|
||||||
domain = it.domain
|
|
||||||
nspace = it.nspace
|
|
||||||
alias = it.alias
|
|
||||||
blob = it.blob
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val threadA = CompletableFuture.supplyAsync {
|
|
||||||
original.generateKey(teeDescriptor, teeAttestKey, rawParams, 0, byteArrayOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
val swDescriptor = KeyDescriptor().apply {
|
|
||||||
domain = keyDescriptor.domain
|
|
||||||
nspace = secureRandom.nextLong()
|
|
||||||
alias = keyDescriptor.alias
|
|
||||||
blob = keyDescriptor.blob
|
|
||||||
}
|
|
||||||
val swKeyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
|
||||||
|
|
||||||
val threadB = CompletableFuture.supplyAsync {
|
|
||||||
doSoftwareKeyGen(callingUid, swDescriptor, attestationKey, parsedParams, swKeyId, isAttestKeyRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val teeMetadata = threadA.join()
|
|
||||||
threadB.cancel(true)
|
|
||||||
teePathDecision.compareAndSet(null, true)
|
|
||||||
SystemLogger.info("AUTO: TEE succeeded, path locked to PATCH for ${keyDescriptor.alias}")
|
|
||||||
|
|
||||||
val originalChain = CertificateHelper.getCertificateChain(teeMetadata)
|
|
||||||
if (originalChain != null && originalChain.size > 1) {
|
|
||||||
val newChain = AttestationPatcher.patchCertificateChain(
|
|
||||||
originalChain, callingUid, parsedParams.certificateNotBefore, parsedParams.certificateNotAfter
|
|
||||||
)
|
|
||||||
CertificateHelper.updateCertificateChain(teeMetadata, newChain).getOrThrow()
|
|
||||||
teeMetadata.authorizations =
|
|
||||||
InterceptorUtils.patchAuthorizations(teeMetadata.authorizations, callingUid)
|
|
||||||
cleanupKeyData(keyId)
|
|
||||||
patchedChains[keyId] = newChain
|
|
||||||
}
|
|
||||||
|
|
||||||
teeResponses[keyId] = KeyEntryResponse().apply {
|
|
||||||
this.metadata = teeMetadata
|
|
||||||
iSecurityLevel = original
|
|
||||||
}
|
|
||||||
|
|
||||||
InterceptorUtils.createTypedObjectReply(teeMetadata)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
if (teePathDecision.get() == true) {
|
|
||||||
threadB.cancel(true)
|
|
||||||
SystemLogger.info("AUTO: TEE failed locally but globally functional, forwarding for ${keyDescriptor.alias}")
|
|
||||||
return TransactionResult.Continue
|
|
||||||
}
|
|
||||||
teePathDecision.compareAndSet(null, false)
|
|
||||||
SystemLogger.info("AUTO: TEE failed, path locked to GENERATE for ${keyDescriptor.alias}")
|
|
||||||
try {
|
|
||||||
threadB.join()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("AUTO: both paths failed for ${keyDescriptor.alias}.", e)
|
|
||||||
val code =
|
|
||||||
if (e.cause is android.os.ServiceSpecificException)
|
|
||||||
(e.cause as android.os.ServiceSpecificException).errorCode
|
|
||||||
else SECURE_HW_COMMUNICATION_FAILED
|
|
||||||
InterceptorUtils.createServiceSpecificErrorReply(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateAttestedKeyPairNative(
|
private fun generateAttestedKeyPairNative(
|
||||||
@@ -1329,15 +1245,23 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HAL-enforced authorization ordering mirrors AOSP keymint reference
|
||||||
|
// HAL output: PURPOSE → ALGORITHM → KEY_SIZE → curve → mode params →
|
||||||
|
// exponent. Duck-Detector's generate-mode fingerprint walks the reply
|
||||||
|
// parcel at 12-byte parser strides and matches when slot[count-1] reads
|
||||||
|
// (secLevel=256, tag=1, unionTag=32) — which emerges in the original
|
||||||
|
// order because EC P-256's KEY_SIZE.value=256 lands at byte 224 (auth#4
|
||||||
|
// value field). Reordering moves KEY_SIZE to auth#2, so byte 224 reads
|
||||||
|
// a different field entirely.
|
||||||
|
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
||||||
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)))
|
||||||
if (this.ecCurve != null) {
|
if (this.ecCurve != null) {
|
||||||
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
||||||
}
|
}
|
||||||
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
|
||||||
this.blockMode.forEach { authList.add(createAuth(Tag.BLOCK_MODE, KeyParameterValue.blockMode(it))) }
|
this.blockMode.forEach { authList.add(createAuth(Tag.BLOCK_MODE, KeyParameterValue.blockMode(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))) }
|
this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) }
|
||||||
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
|
||||||
if (this.rsaPublicExponent != null) {
|
if (this.rsaPublicExponent != null) {
|
||||||
authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
|
authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,18 +101,19 @@ object CertificateGenerator {
|
|||||||
|
|
||||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||||
|
|
||||||
val (signingKey, issuer) =
|
val attestKeyInfo =
|
||||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
getAttestationKeyInfo(uid, attestKeyAlias)
|
||||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
} else null
|
||||||
} else {
|
|
||||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
val (signingKey, issuer) = attestKeyInfo
|
||||||
}
|
?.let { it.first to it.second }
|
||||||
|
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||||
|
|
||||||
val leafCert =
|
val leafCert =
|
||||||
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||||
|
|
||||||
if (attestKeyAlias != null) {
|
if (attestKeyInfo != null) {
|
||||||
listOf(leafCert)
|
listOf(leafCert)
|
||||||
} else {
|
} else {
|
||||||
listOf(leafCert) + keybox.certificates
|
listOf(leafCert) + keybox.certificates
|
||||||
|
|||||||
+11
-25
@@ -17,36 +17,22 @@ echo " "
|
|||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
vol_tmp="${TMPDIR:-/data/local/tmp}/teesim_vol_key"
|
vol_tmp="${TMPDIR:-/data/local/tmp}/teesim_vol_key"
|
||||||
seconds=10
|
|
||||||
|
|
||||||
: > "$vol_tmp"
|
: > "$vol_tmp"
|
||||||
getevent -qlc 1 > "$vol_tmp" 2>/dev/null &
|
|
||||||
ge_pid=$!
|
|
||||||
|
|
||||||
while [ "$seconds" -gt 0 ]; do
|
# Stream getevent and match VOLUME DOWN inline. Single-event sampling
|
||||||
sleep 1
|
# (`getevent -c 1`) races with EV_SYN/EV_MSC noise on Magisk's BusyBox ash.
|
||||||
if ! kill -0 "$ge_pid" 2>/dev/null; then
|
/system/bin/timeout 10 /system/bin/sh -c '
|
||||||
key=$(awk '/KEY_/{print $3}' "$vol_tmp" 2>/dev/null)
|
/system/bin/getevent -lq 2>/dev/null | while IFS= read -r line; do
|
||||||
case "$key" in
|
case "$line" in
|
||||||
KEY_VOLUMEUP)
|
*KEY_VOLUMEUP*DOWN*) echo UP > "$1"; exit 0 ;;
|
||||||
rm -f "$vol_tmp"
|
*KEY_VOLUMEDOWN*DOWN*) echo DOWN > "$1"; exit 0 ;;
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
KEY_VOLUMEDOWN)
|
|
||||||
rm -f "$vol_tmp"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
: > "$vol_tmp"
|
done
|
||||||
getevent -qlc 1 > "$vol_tmp" 2>/dev/null &
|
' _ "$vol_tmp"
|
||||||
ge_pid=$!
|
|
||||||
fi
|
|
||||||
seconds=$((seconds - 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
kill "$ge_pid" 2>/dev/null
|
key=$(cat "$vol_tmp" 2>/dev/null)
|
||||||
wait "$ge_pid" 2>/dev/null
|
|
||||||
rm -f "$vol_tmp"
|
rm -f "$vol_tmp"
|
||||||
|
[ "$key" = "UP" ] && return 0
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,80 @@
|
|||||||
|
## TEESimulator-RS v6.0.0-235
|
||||||
|
|
||||||
|
11 commits since v6.0.0-224. Duck Detector generate-mode fingerprint cleared. Shizuku-routed BYO attestation fixed. Vol-key confirmation restored on Magisk.
|
||||||
|
|
||||||
|
### Detection Coverage
|
||||||
|
- Duck Detector "TEE Simulator generate-mode fingerprint" cleared. `toAuthorizations` reordered to AOSP keymint reference order; KEY_SIZE moves from auth#4 to auth#2, breaking the byte-224 anchor the probe relied on. 0/31 matches on fresh self-probes (was 15/36).
|
||||||
|
- `persist.logd.size` variants blanked at boot via `service.sh`. Removes a logd-tuning side-channel.
|
||||||
|
|
||||||
|
### BYO & Shizuku Routing
|
||||||
|
- Shizuku-routed BYO attestation no longer fails with `-49 UNSUPPORTED_TAG`. `shouldSkipUid` moved into `handleGenerateKey`, evaluated after BYO parameters are parsed.
|
||||||
|
- `createOperation` parallel fix: outer UID gate removed; the cache-or-forward lookup is the sole gate. BYO keys created under Shizuku UID can now be used for signing under the same UID.
|
||||||
|
- `forceGenerate` simplified: any attest-key or BYO request routes to software unconditionally.
|
||||||
|
- BYO attest-key miss returns the full keybox chain instead of a malformed depth-1 chain.
|
||||||
|
- AUTO TEE race dispatch removed. Resolution uses `DeviceAttestationService.isTeeFunctional` only.
|
||||||
|
- Symmetric gen rejects `attestationKey != null` early with `INVALID_ARGUMENT`. Unsupported-algorithm branch returns `-38` instead of `-49`.
|
||||||
|
|
||||||
|
### Action Button
|
||||||
|
- Vol+ / Vol- confirmation restored on Magisk. Streaming `getevent -lq` matched inline against `KEY_VOLUMEUP DOWN` / `KEY_VOLUMEDOWN DOWN`, wrapped in `/system/bin/timeout 10`. The prior polled approach timed out on six-events-per-keypress kernels.
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
- Android 15 (SDK 35), daemon PID 1466.
|
||||||
|
- Cross-device confirmation pending on OnePlus PKX110 and Samsung SM-S928B.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TEESimulator-RS v6.0.0-224
|
||||||
|
|
||||||
|
59 commits since v6.0.0-162. Self-sufficient spoofing infrastructure, Duck Detector TamperScore-4 cleared on Xiaomi A16, persistent symmetric key storage (PR #22), 22-language action button hardening.
|
||||||
|
|
||||||
|
### Detection Coverage
|
||||||
|
- Duck Detector TimingSideChannelProbe cleared on Xiaomi A16 (SDK 35). Timing ratio dropped 1.555x to 1.055x, verdict WARNING to CLEAR. Threshold is > 1.1x.
|
||||||
|
- `KEY_ID` resolved from `teeResponses` instead of synthesized, matching real KeyMint binder behavior.
|
||||||
|
- Non-attested key cache mirrors attested path for byte-level metadata parity.
|
||||||
|
- `KEY_SIZE` emitted for EC keys; omitted when `ecCurve` is present, matching AOSP attestation_record.h.
|
||||||
|
- SSE messages synthesized canonically on non-AEAD `updateAad`; passthrough shape normalized.
|
||||||
|
- StrongBox attest version no longer hardcoded; resolved from device context.
|
||||||
|
- TEE op latency floor enforced to defeat micro-timing probes.
|
||||||
|
- Attest key resolution restored to nspace-aware lookup after revert/restore cycle.
|
||||||
|
|
||||||
|
### Self-Sufficient Spoofing
|
||||||
|
- `PatchLevelManager` resolves OS/VENDOR/BOOT patch levels via PIF without external bulletin fetch.
|
||||||
|
- `BulletinPoller` refreshes bulletin data on a schedule, isolated from boot path via umbrella `try/catch`.
|
||||||
|
- Bootloader-lock props pushed via `resetprop` at boot; absent vbmeta complement props filled; `vbmeta.device_state` included.
|
||||||
|
- PIF hot-reload via `FileObserver`; empty source files skipped; future patch dates bounded by `MAX_FUTURE_DAYS`.
|
||||||
|
- Default `security_patch.txt` dropped at install time.
|
||||||
|
- `sepolicy.rule` allows UDP egress for DNS resolution.
|
||||||
|
|
||||||
|
### Key Persistence (PR #22)
|
||||||
|
- Symmetric keys persist across reboots with byte-identical metadata.
|
||||||
|
- Keybox edits no longer wipe stored keys.
|
||||||
|
- Delete marker dropped on key regeneration to prevent stale state.
|
||||||
|
- Defensive symmetric fallback path with clean error codes.
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
- `atomicWrite` preserves `[pkg]` sections; errors guarded in `updateTo`.
|
||||||
|
- `applyToProps` serialized against concurrent callers.
|
||||||
|
- `pollOnce` wrapped in umbrella `try/catch`; `BulletinPoller.start` failure isolated from spoofer init.
|
||||||
|
- Spoofer ordering fixed: runs before keystore hook to prevent attest-time prop drift.
|
||||||
|
- `isAutoMode` reads raw package mode; `system=prop` passive default respected.
|
||||||
|
- `mergedContents` propagates read errors instead of swallowing them.
|
||||||
|
- Date regex validation on `currentPatch`; YYYY-MM input skips day synthesis.
|
||||||
|
- Global key-assignment check requires `=` delimiter (no more partial matches).
|
||||||
|
- `validation_rejected` status emitted on invalid spoof input.
|
||||||
|
|
||||||
|
### Action Button UX
|
||||||
|
- Vol+ required to clear `persistent_keys`. Vol- cancels. 10-second timeout defaults to cancel.
|
||||||
|
- Confirmation localized in 22 languages: ar, az, bn, de, el, es-ES, fa, fr, id, it, ja, ko, pl, pt-BR, ru, th, tl, tr, uk, vi, zh-CN, zh-TW.
|
||||||
|
- Every echoed string resolves through `_msg()` against device locale.
|
||||||
|
|
||||||
|
### Build & Ops
|
||||||
|
- Kotlin `jvmTarget` raised to JVM 21.
|
||||||
|
- Gradle auto-rewrites `module/update.json` on packaging.
|
||||||
|
- `scripts/package.sh` locates user-local cargo; rust task receives cargo bin path.
|
||||||
|
- Verified on Xiaomi Android 16 (SDK 35) `v6.0.0-224-Release`. Daemon alive PID 1392. Pending cross-device confirm on OnePlus PKX110 (qcom sun) and Samsung SM-S928B (pineapple).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TEESimulator-RS v6.0.0
|
## TEESimulator-RS v6.0.0
|
||||||
|
|
||||||
Repository consolidation release. All tee-rebuild work merged as the new main branch.
|
Repository consolidation release. All tee-rebuild work merged as the new main branch.
|
||||||
|
|||||||
@@ -3,3 +3,14 @@ cd $MODDIR
|
|||||||
|
|
||||||
# Fork-based supervisor for instant restart
|
# Fork-based supervisor for instant restart
|
||||||
./supervisor ./daemon "$MODDIR" &
|
./supervisor ./daemon "$MODDIR" &
|
||||||
|
|
||||||
|
# Clear logd size persist properties once boot completes
|
||||||
|
(
|
||||||
|
until [ "$(getprop sys.boot_completed)" = "1" ]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
setprop persist.logd.size ""
|
||||||
|
setprop persist.logd.size.crash ""
|
||||||
|
setprop persist.logd.size.system ""
|
||||||
|
setprop persist.logd.size.main ""
|
||||||
|
) &
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "v6.0.0-211",
|
"version": "v6.0.0-235",
|
||||||
"versionCode": 211,
|
"versionCode": 235,
|
||||||
"zipUrl": "https://github.com/Enginex0/TEESimulator-RS/releases/download/v6.0.0-211/TEESimulator-RS-v6.0.0-211-Release.zip",
|
"zipUrl": "https://github.com/Enginex0/TEESimulator-RS/releases/download/v6.0.0-235/TEESimulator-RS-v6.0.0-235-Release.zip",
|
||||||
"changelog": "https://raw.githubusercontent.com/Enginex0/TEESimulator-RS/main/module/changelog.md"
|
"changelog": "https://raw.githubusercontent.com/Enginex0/TEESimulator-RS/main/module/changelog.md"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user