Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b8a92fbd | ||
|
|
0723865eab | ||
|
|
7cb44b9999 | ||
|
|
eddd9908af | ||
|
|
36ccd22cdc | ||
|
|
81e6fbf97e | ||
|
|
7f63713f07 | ||
|
|
5df76eacd1 |
@@ -1 +1,7 @@
|
|||||||
out
|
out
|
||||||
|
.gradle
|
||||||
|
.kotlin
|
||||||
|
app/build
|
||||||
|
build
|
||||||
|
native-certgen/target
|
||||||
|
app/src/main/jniLibs
|
||||||
|
|||||||
@@ -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.7"
|
val verName = "v4.8"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
|
|||||||
+113
-18
@@ -1,9 +1,11 @@
|
|||||||
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.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.KeyOrigin
|
||||||
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
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
|
||||||
@@ -17,6 +19,7 @@ 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.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||||
import org.matrix.TEESimulator.attestation.AttestationConstants
|
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||||
@@ -33,6 +36,7 @@ import org.matrix.TEESimulator.pki.CertificateHelper
|
|||||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||||
import org.matrix.TEESimulator.pki.NativeCertGen
|
import org.matrix.TEESimulator.pki.NativeCertGen
|
||||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||||
|
import org.matrix.TEESimulator.util.AndroidPermissionUtils
|
||||||
|
|
||||||
class KeyMintSecurityLevelInterceptor(
|
class KeyMintSecurityLevelInterceptor(
|
||||||
private val original: IKeystoreSecurityLevel,
|
private val original: IKeystoreSecurityLevel,
|
||||||
@@ -45,6 +49,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val response: KeyEntryResponse,
|
val response: KeyEntryResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||||
|
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||||
|
|
||||||
override fun onPreTransact(
|
override fun onPreTransact(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
@@ -192,42 +199,90 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pruneOpsForUid(uid: Int, newOp: SoftwareOperation, maxOps: Int = MAX_CONCURRENT_OPS_PER_UID) {
|
||||||
|
val ops = activeOps.computeIfAbsent(uid) { ConcurrentLinkedDeque() }
|
||||||
|
val before = ops.size
|
||||||
|
ops.removeIf { it.finalized }
|
||||||
|
val afterClean = ops.size
|
||||||
|
while (ops.size >= maxOps) {
|
||||||
|
val oldest = ops.pollFirst() ?: break
|
||||||
|
if (!oldest.finalized) {
|
||||||
|
SystemLogger.info("[LRU] Pruning operation for uid=$uid (active=${ops.size}/$maxOps)")
|
||||||
|
oldest.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ops.addLast(newOp)
|
||||||
|
SystemLogger.debug("[LRU] uid=$uid ops: before=$before cleaned=${before - afterClean} active=${ops.size}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun trackAndEnforceOpLimit(callingUid: Int, txId: Long): TransactionResult? {
|
||||||
|
if (securityLevel != SecurityLevel.STRONGBOX) return null
|
||||||
|
val timestamps = recentOps.computeIfAbsent(callingUid) { ConcurrentLinkedDeque() }
|
||||||
|
val cutoff = System.nanoTime() - STRONGBOX_OP_WINDOW_NS
|
||||||
|
timestamps.removeIf { it < cutoff }
|
||||||
|
val swOps = activeOps[callingUid]?.count { !it.finalized } ?: 0
|
||||||
|
if (timestamps.size + swOps >= STRONGBOX_MAX_CONCURRENT_OPS) {
|
||||||
|
SystemLogger.info("[TX_ID: $txId] StrongBox op limit reached for uid=$callingUid (hw=${timestamps.size} sw=$swOps max=$STRONGBOX_MAX_CONCURRENT_OPS)")
|
||||||
|
return InterceptorUtils.createErrorReply(KEYMINT_TOO_MANY_OPERATIONS)
|
||||||
|
}
|
||||||
|
timestamps.addLast(System.nanoTime())
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCreateOperation(
|
private fun handleCreateOperation(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
|
SystemLogger.debug("[TX_ID: $txId] createOperation parcel: dataSize=${data.dataSize()} dataAvail=${data.dataAvail()} dataPos=${data.dataPosition()}")
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
|
|
||||||
// An operation must use the KEY_ID domain.
|
SystemLogger.debug("[TX_ID: $txId] createOperation descriptor: domain=${keyDescriptor.domain} nspace=${keyDescriptor.nspace} alias=${keyDescriptor.alias}")
|
||||||
if (keyDescriptor.domain != Domain.KEY_ID) {
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
// Android framework calls createOperation with domain=APP+alias;
|
||||||
|
// keystore2 internally resolves to KEY_ID — but software keys never
|
||||||
|
// reach keystore2's database, so we must handle both lookup paths.
|
||||||
|
val generatedKeyInfo = when (keyDescriptor.domain) {
|
||||||
|
Domain.APP -> {
|
||||||
|
val alias = keyDescriptor.alias ?: run {
|
||||||
|
SystemLogger.info("[TX_ID: $txId] createOperation domain=APP with null alias, forwarding to HAL")
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
generatedKeys[KeyIdentifier(callingUid, alias)] ?: run {
|
||||||
|
SystemLogger.info("[TX_ID: $txId] createOperation alias=$alias not in generatedKeys, forwarding to HAL")
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Domain.KEY_ID -> {
|
||||||
|
findGeneratedKeyByKeyId(callingUid, keyDescriptor.nspace) ?: run {
|
||||||
|
trackAndEnforceOpLimit(callingUid, txId)?.let { return it }
|
||||||
|
SystemLogger.info("[TX_ID: $txId] createOperation KeyId(${keyDescriptor.nspace}) NOT FOUND for uid=$callingUid. Forwarding to HAL.")
|
||||||
|
return TransactionResult.Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
SystemLogger.info("[TX_ID: $txId] createOperation domain=${keyDescriptor.domain}, forwarding to HAL")
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val nspace = keyDescriptor.nspace
|
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for uid=$callingUid.")
|
||||||
val generatedKeyInfo = findGeneratedKeyByKeyId(callingUid, nspace)
|
|
||||||
|
|
||||||
if (generatedKeyInfo == null) {
|
|
||||||
SystemLogger.debug(
|
|
||||||
"[TX_ID: $txId] Operation for unknown/hardware KeyId ($nspace). Forwarding."
|
|
||||||
)
|
|
||||||
return TransactionResult.Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.")
|
|
||||||
|
|
||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
val parsedParams = KeyMintAttestation(params).let { p ->
|
val parsedParams = KeyMintAttestation(params).let { p ->
|
||||||
if (p.algorithm != 0) p
|
if (p.algorithm != 0) p
|
||||||
else p.copy(algorithm = when (generatedKeyInfo.keyPair.private.algorithm) {
|
else p.copy(algorithm = when (generatedKeyInfo.keyPair.private.algorithm) {
|
||||||
"EC" -> Algorithm.EC
|
"EC", "ECDSA" -> Algorithm.EC
|
||||||
"RSA" -> Algorithm.RSA
|
"RSA" -> Algorithm.RSA
|
||||||
else -> p.algorithm
|
else -> p.algorithm
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams)
|
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
|
||||||
|
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams, opLatency)
|
||||||
|
val maxOps = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_MAX_CONCURRENT_OPS else MAX_CONCURRENT_OPS_PER_UID
|
||||||
|
pruneOpsForUid(callingUid, softwareOperation, maxOps)
|
||||||
val operationBinder = SoftwareOperationBinder(softwareOperation)
|
val operationBinder = SoftwareOperationBinder(softwareOperation)
|
||||||
|
|
||||||
val response =
|
val response =
|
||||||
@@ -267,11 +322,38 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
return InterceptorUtils.createErrorReply(RESPONSE_INVALID_ARGUMENT)
|
return InterceptorUtils.createErrorReply(RESPONSE_INVALID_ARGUMENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.any { it.tag == Tag.DEVICE_UNIQUE_ATTESTATION }) {
|
if (params.any { it.tag == Tag.DEVICE_UNIQUE_ATTESTATION } && !AndroidPermissionUtils.hasUniqueIdAttestationPermission(callingUid)) {
|
||||||
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_UNIQUE_ATTESTATION for uid=$callingUid")
|
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_UNIQUE_ATTESTATION for uid=$callingUid")
|
||||||
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
|
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasDeviceIdAttestation = params.any {
|
||||||
|
it.tag == Tag.ATTESTATION_ID_IMEI ||
|
||||||
|
it.tag == Tag.ATTESTATION_ID_MEID ||
|
||||||
|
it.tag == Tag.ATTESTATION_ID_SERIAL ||
|
||||||
|
it.tag == Tag.DEVICE_UNIQUE_ATTESTATION ||
|
||||||
|
it.tag == Tag.ATTESTATION_ID_SECOND_IMEI
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasDeviceIdAttestation && !AndroidPermissionUtils.hasDeviceAttestationPermission(callingUid)) {
|
||||||
|
SystemLogger.warning("[TX_ID: $txId] Rejecting DEVICE_ID_ATTESTATION for uid=$callingUid")
|
||||||
|
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isSymmetric = parsedParams.algorithm == Algorithm.AES ||
|
||||||
|
parsedParams.algorithm == Algorithm.HMAC ||
|
||||||
|
parsedParams.algorithm == Algorithm.TRIPLE_DES
|
||||||
|
|
||||||
|
if (isSymmetric) {
|
||||||
|
SystemLogger.debug("[TX_ID: $txId] Symmetric algorithm ${parsedParams.algorithm} → forwarding to HAL")
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
|
||||||
|
if (securityLevel == SecurityLevel.STRONGBOX && !isStrongBoxCapable(parsedParams)) {
|
||||||
|
SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}) → forwarding to HAL for rejection")
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
val isAttestKeyRequest = parsedParams.isAttestKey()
|
val isAttestKeyRequest = parsedParams.isAttestKey()
|
||||||
|
|
||||||
@@ -357,7 +439,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
|
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
|
||||||
val delayMs = TEE_LATENCY_FLOOR_MS - elapsedMs
|
val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS
|
||||||
|
val delayMs = floor - elapsedMs
|
||||||
if (delayMs > 0) Thread.sleep(delayMs)
|
if (delayMs > 0) Thread.sleep(delayMs)
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||||
@@ -549,7 +632,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
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 TEE_LATENCY_FLOOR_MS = 15L
|
||||||
|
private const val STRONGBOX_KEYGEN_LATENCY_FLOOR_MS = 250L
|
||||||
|
private const val STRONGBOX_OP_LATENCY_FLOOR_MS = 80L
|
||||||
|
private const val KEYMINT_TOO_MANY_OPERATIONS = -29
|
||||||
private const val KEYMINT_CANNOT_ATTEST_IDS = -66
|
private const val KEYMINT_CANNOT_ATTEST_IDS = -66
|
||||||
|
private const val MAX_CONCURRENT_OPS_PER_UID = 15
|
||||||
|
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
|
||||||
|
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s
|
||||||
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
|
||||||
@@ -558,6 +647,12 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
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>>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
private fun hardwareKeygenCount(uid: Int): AtomicInteger =
|
private fun hardwareKeygenCount(uid: Int): AtomicInteger =
|
||||||
uidHardwareKeygenCount.computeIfAbsent(uid) { AtomicInteger(0) }
|
uidHardwareKeygenCount.computeIfAbsent(uid) { AtomicInteger(0) }
|
||||||
|
|
||||||
|
|||||||
+38
-16
@@ -34,16 +34,17 @@ private object JcaAlgorithmMapper {
|
|||||||
Digest.SHA_2_512 -> "SHA512"
|
Digest.SHA_2_512 -> "SHA512"
|
||||||
else -> "NONE"
|
else -> "NONE"
|
||||||
}
|
}
|
||||||
val keyAlgo =
|
return when (params.algorithm) {
|
||||||
when (params.algorithm) {
|
Algorithm.EC -> "${digest}withECDSA"
|
||||||
Algorithm.EC -> "ECDSA"
|
Algorithm.RSA -> {
|
||||||
Algorithm.RSA -> "RSA"
|
val isPss = params.padding.firstOrNull() == PaddingMode.RSA_PSS
|
||||||
else ->
|
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
|
||||||
throw IllegalArgumentException(
|
|
||||||
"Unsupported signature algorithm: ${params.algorithm}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return "${digest}with${keyAlgo}"
|
else ->
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Unsupported signature algorithm: ${params.algorithm}"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
|
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
|
||||||
@@ -60,16 +61,18 @@ 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()) {
|
||||||
PaddingMode.NONE -> "NoPadding"
|
PaddingMode.NONE -> "NoPadding"
|
||||||
PaddingMode.PKCS7 -> "PKCS7Padding"
|
PaddingMode.PKCS7 -> "PKCS7Padding"
|
||||||
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
|
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
|
||||||
|
PaddingMode.RSA_PKCS1_1_5_SIGN -> "PKCS1Padding"
|
||||||
PaddingMode.RSA_OAEP -> "OAEPPadding"
|
PaddingMode.RSA_OAEP -> "OAEPPadding"
|
||||||
else -> "NoPadding" // Default for GCM
|
else -> "NoPadding"
|
||||||
}
|
}
|
||||||
return "$keyAlgo/$blockMode/$padding"
|
return "$keyAlgo/$blockMode/$padding"
|
||||||
}
|
}
|
||||||
@@ -142,9 +145,15 @@ private class CipherPrimitive(
|
|||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) {
|
class SoftwareOperation(
|
||||||
|
private val txId: Long,
|
||||||
|
keyPair: KeyPair,
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
private val latencyFloorMs: Long = 0L,
|
||||||
|
) {
|
||||||
private val primitive: CryptoPrimitive
|
private val primitive: CryptoPrimitive
|
||||||
@Volatile private var finalized = false
|
@Volatile var finalized = false
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val purpose = params.purpose.firstOrNull()
|
val purpose = params.purpose.firstOrNull()
|
||||||
@@ -163,21 +172,28 @@ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMin
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkActive() {
|
private fun checkActive() {
|
||||||
if (finalized) throw ServiceSpecificException(KeystoreErrorCodes.invalidOperationHandle)
|
if (finalized) {
|
||||||
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Rejected: operation already finalized (pruned or completed)")
|
||||||
|
throw ServiceSpecificException(KeystoreErrorCodes.invalidOperationHandle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkInputLength(data: ByteArray?) {
|
private fun checkInputLength(data: ByteArray?) {
|
||||||
if (data != null && data.size > MAX_RECEIVE_DATA)
|
if (data != null && data.size > MAX_RECEIVE_DATA) {
|
||||||
|
SystemLogger.info("[SoftwareOp TX_ID: $txId] Input too large: ${data.size} > $MAX_RECEIVE_DATA, throwing TOO_MUCH_DATA(${KeystoreErrorCodes.tooMuchData})")
|
||||||
throw ServiceSpecificException(KeystoreErrorCodes.tooMuchData)
|
throw ServiceSpecificException(KeystoreErrorCodes.tooMuchData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAad(aadInput: ByteArray?) {
|
fun updateAad(aadInput: ByteArray?) {
|
||||||
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] updateAad() inputSize=${aadInput?.size ?: 0}")
|
||||||
checkActive()
|
checkActive()
|
||||||
checkInputLength(aadInput)
|
checkInputLength(aadInput)
|
||||||
primitive.updateAad(aadInput)
|
primitive.updateAad(aadInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(data: ByteArray?): ByteArray? {
|
fun update(data: ByteArray?): ByteArray? {
|
||||||
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] update() inputSize=${data?.size ?: 0}")
|
||||||
checkActive()
|
checkActive()
|
||||||
checkInputLength(data)
|
checkInputLength(data)
|
||||||
try {
|
try {
|
||||||
@@ -194,7 +210,13 @@ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMin
|
|||||||
checkActive()
|
checkActive()
|
||||||
checkInputLength(data)
|
checkInputLength(data)
|
||||||
try {
|
try {
|
||||||
|
val startNs = if (latencyFloorMs > 0) System.nanoTime() else 0L
|
||||||
val result = primitive.finish(data, signature)
|
val result = primitive.finish(data, signature)
|
||||||
|
if (latencyFloorMs > 0) {
|
||||||
|
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
|
||||||
|
val delayMs = latencyFloorMs - elapsedMs
|
||||||
|
if (delayMs > 0) Thread.sleep(delayMs)
|
||||||
|
}
|
||||||
finalized = true
|
finalized = true
|
||||||
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
||||||
return result
|
return result
|
||||||
@@ -220,7 +242,7 @@ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMin
|
|||||||
|
|
||||||
private object KeystoreErrorCodes {
|
private object KeystoreErrorCodes {
|
||||||
val tooMuchData: Int by lazy {
|
val tooMuchData: Int by lazy {
|
||||||
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 29)
|
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
|
||||||
}
|
}
|
||||||
|
|
||||||
val invalidOperationHandle: Int by lazy {
|
val invalidOperationHandle: Int by lazy {
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ object CertificateGenerator {
|
|||||||
|
|
||||||
val signerAlgorithm =
|
val signerAlgorithm =
|
||||||
when (signingKeyPair.private.algorithm) {
|
when (signingKeyPair.private.algorithm) {
|
||||||
"EC" -> "SHA256withECDSA"
|
"EC", "ECDSA" -> "SHA256withECDSA"
|
||||||
"RSA" -> "SHA256withRSA"
|
"RSA" -> "SHA256withRSA"
|
||||||
else -> throw IllegalArgumentException("Unsupported signing key: ${signingKeyPair.private.algorithm}")
|
else -> throw IllegalArgumentException("Unsupported signing key: ${signingKeyPair.private.algorithm}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ object NativeCertGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val algorithmName = when (certs[0].publicKey.algorithm) {
|
val algorithmName = when (certs[0].publicKey.algorithm) {
|
||||||
"EC" -> "EC"
|
"EC", "ECDSA" -> "EC"
|
||||||
"RSA" -> "RSA"
|
"RSA" -> "RSA"
|
||||||
else -> certs[0].publicKey.algorithm
|
else -> certs[0].publicKey.algorithm
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package org.matrix.TEESimulator.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
|
object AndroidPermissionUtils {
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
|
||||||
|
private fun getGlobalContext(): Context? {
|
||||||
|
return try {
|
||||||
|
// 1. Get the hidden ActivityThread class via reflection
|
||||||
|
val activityThreadClass = Class.forName("android.app.ActivityThread")
|
||||||
|
|
||||||
|
// 2. Invoke the static currentActivityThread() method
|
||||||
|
val currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread")
|
||||||
|
currentActivityThreadMethod.isAccessible = true
|
||||||
|
val activityThread = currentActivityThreadMethod.invoke(null)
|
||||||
|
|
||||||
|
if (activityThread == null) {
|
||||||
|
SystemLogger.warning("Reflection: ActivityThread.currentActivityThread() returned null")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Try to get the application context
|
||||||
|
val getApplicationMethod = activityThreadClass.getDeclaredMethod("getApplication")
|
||||||
|
getApplicationMethod.isAccessible = true
|
||||||
|
val application = getApplicationMethod.invoke(activityThread) as? Context
|
||||||
|
|
||||||
|
if (application != null) return application
|
||||||
|
|
||||||
|
// 4. Fallback to getSystemContext() if application is null (often happens in system_server)
|
||||||
|
val getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext")
|
||||||
|
getSystemContextMethod.isAccessible = true
|
||||||
|
getSystemContextMethod.invoke(activityThread) as? Context
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Reflection failed to get global context for permission check", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core permission check.
|
||||||
|
*/
|
||||||
|
fun hasPermission(uid: Int, permission: String): Boolean {
|
||||||
|
val context = getGlobalContext() ?: run {
|
||||||
|
SystemLogger.warning("AndroidPermissionUtils: Context is null, failing permission check safely.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = context.checkPermission(permission, -1, uid)
|
||||||
|
return result == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasDeviceAttestationPermission(uid: Int): Boolean {
|
||||||
|
return hasPermission(uid, "android.permission.READ_PRIVILEGED_PHONE_STATE")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasUniqueIdAttestationPermission(uid: Int): Boolean {
|
||||||
|
return hasPermission(uid, "android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasManageUsersPermission(uid: Int): Boolean {
|
||||||
|
return hasPermission(uid, "android.permission.MANAGE_USERS")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasDumpPermission(uid: Int): Boolean {
|
||||||
|
return hasPermission(uid, "android.permission.DUMP")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user