diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b87329..cce73d4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,7 +29,7 @@ val gitExecutor = objects.newInstance(GitExecutor::class.java) val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt() val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir) -val verName = "v3.2" +val verName = "v5.1" android { namespace = "org.matrix.TEESimulator" @@ -79,13 +79,13 @@ androidComponents { // --- Define output locations and file names --- // Stage all files in a temporary directory inside 'build' before zipping val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}") - val zipFileName = "TEESimulator-$verName-$gitCommitCount-$gitCommitHash-$capitalized.zip" + val zipFileName = "TEESimulator-RS-$verName-$gitCommitCount-$gitCommitHash-$capitalized.zip" // Task 1: Prepare all module files in the temporary build directory. // Using Sync ensures that stale files from previous runs are removed. val prepareModuleFilesTask = tasks.register("prepareModuleFiles${capitalized}") { - group = "TEESimulator Module Packaging" + group = "TEESimulator-RS Module Packaging" description = "Prepares all files for the ${variant.name} module zip." if (isDebug) { @@ -143,7 +143,7 @@ androidComponents { // Task 2: Zip the prepared files from the temporary directory. val zipTask = tasks.register("zip${capitalized}") { - group = "TEESimulator Module Packaging" + group = "TEESimulator-RS Module Packaging" description = "Creates the flashable zip for the ${variant.name} module." dependsOn(prepareModuleFilesTask) @@ -156,7 +156,7 @@ androidComponents { fun createInstallTasks(rootProvider: String, installCli: String) { val pushTask = tasks.register("push${rootProvider}Module${capitalized}") { - group = "TEESimulator Module Installation" + group = "TEESimulator-RS Module Installation" description = "Pushes the ${variant.name} module to the device for $rootProvider." dependsOn(zipTask) @@ -170,7 +170,7 @@ androidComponents { val installTask = tasks.register("install${rootProvider}${capitalized}") { - group = "TEESimulator Module Installation" + group = "TEESimulator-RS Module Installation" description = "Installs the ${variant.name} module via $rootProvider." dependsOn(pushTask) commandLine( @@ -183,7 +183,7 @@ androidComponents { } tasks.register("install${rootProvider}AndReboot${capitalized}") { - group = "TEESimulator Module Installation" + group = "TEESimulator-RS Module Installation" description = "Installs the ${variant.name} module via $rootProvider and reboots." dependsOn(installTask) commandLine("adb", "reboot") diff --git a/app/src/main/java/org/matrix/TEESimulator/App.kt b/app/src/main/java/org/matrix/TEESimulator/App.kt index 0ff5675..1c7f0ce 100644 --- a/app/src/main/java/org/matrix/TEESimulator/App.kt +++ b/app/src/main/java/org/matrix/TEESimulator/App.kt @@ -13,6 +13,7 @@ import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor import org.matrix.TEESimulator.logging.SystemLogger +import org.matrix.TEESimulator.pki.NativeCertGen import org.matrix.TEESimulator.util.AndroidDeviceUtils /** @@ -49,6 +50,8 @@ object App { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) Security.addProvider(BouncyCastleProvider()) + NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so") + // This starts the message queue processing. It blocks here indefinitely // processing messages until Looper.myLooper().quit() is called. Looper.loop() diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt index 499a14d..f2539b0 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt @@ -553,7 +553,7 @@ object AttestationBuilder { * retrieved. */ @Throws(Throwable::class) - private fun createApplicationId(uid: Int): DEROctetString { + internal fun createApplicationId(uid: Int): DEROctetString { // AOSP keystore_attestation_id.cpp: gather_attestation_application_id() // uses a hardcoded identity for AID_SYSTEM (1000) and AID_ROOT (0): // packageName = "AndroidSystem", versionCode = 1, no signing digests. diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt index 65917f4..eda4b29 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt @@ -98,6 +98,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { interceptor, KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES, ) + interceptor.loadPersistedKeys() } } .onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) } @@ -114,6 +115,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { interceptor, KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES, ) + interceptor.loadPersistedKeys() } } .onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 5b64fed..29daf3d 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -14,7 +14,14 @@ import java.security.KeyPair import java.security.SecureRandom import java.security.cert.Certificate import java.util.concurrent.CompletableFuture +import android.util.Pair as AndroidPair +import java.io.ByteArrayInputStream +import java.security.KeyFactory +import java.security.cert.CertificateFactory +import java.security.spec.PKCS8EncodedKeySpec import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import org.matrix.TEESimulator.attestation.AttestationBuilder import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.config.ConfigurationManager @@ -22,8 +29,11 @@ import org.matrix.TEESimulator.interception.core.BinderInterceptor import org.matrix.TEESimulator.interception.keystore.InterceptorUtils import org.matrix.TEESimulator.interception.keystore.KeyIdentifier import org.matrix.TEESimulator.logging.SystemLogger +import org.matrix.TEESimulator.pki.CertGenConfig import org.matrix.TEESimulator.pki.CertificateGenerator import org.matrix.TEESimulator.pki.CertificateHelper +import org.matrix.TEESimulator.pki.KeyBoxManager +import org.matrix.TEESimulator.pki.NativeCertGen import org.matrix.TEESimulator.util.AndroidDeviceUtils import org.matrix.TEESimulator.util.TeeLatencySimulator @@ -547,14 +557,18 @@ class KeyMintSecurityLevelInterceptor( return InterceptorUtils.createTypedObjectReply(metadata) } - val keyData = + val keyData = if (NativeCertGen.isAvailable && attestationKey == null) { + generateAttestedKeyPairNative(callingUid, parsedParams) + ?: CertificateGenerator.generateAttestedKeyPair( + callingUid, keyDescriptor.alias, attestationKey?.alias, + parsedParams, securityLevel, + ) + } else { CertificateGenerator.generateAttestedKeyPair( - callingUid, - keyDescriptor.alias, - attestationKey?.alias, - parsedParams, - securityLevel, - ) ?: throw Exception("CertificateGenerator failed to create key pair.") + callingUid, keyDescriptor.alias, attestationKey?.alias, + parsedParams, securityLevel, + ) + } ?: throw Exception("Certificate generation failed.") val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor) @@ -562,12 +576,182 @@ class KeyMintSecurityLevelInterceptor( GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams) if (isAttestKeyRequest) attestationKeys.add(keyId) + val certChainCopy = keyData.second.toList() + persistExecutor.execute { + GeneratedKeyPersistence.save( + keyId = keyId, + keyPair = keyData.first, + nspace = keyDescriptor.nspace, + securityLevel = securityLevel, + certChain = certChainCopy, + algorithm = parsedParams.algorithm, + keySize = parsedParams.keySize, + ecCurve = parsedParams.ecCurve ?: 0, + purposes = parsedParams.purpose, + digests = parsedParams.digest, + isAttestationKey = isAttestKeyRequest, + ) + } + TeeLatencySimulator.simulateGenerateKeyDelay( parsedParams.algorithm, System.nanoTime() - genStartNanos ) return InterceptorUtils.createTypedObjectReply(response.metadata) } + private fun generateAttestedKeyPairNative( + callingUid: Int, + params: KeyMintAttestation, + ): AndroidPair>? { + return runCatching { + val algorithmName = when (params.algorithm) { + Algorithm.EC -> "EC" + Algorithm.RSA -> "RSA" + else -> return null + } + val keyboxFile = ConfigurationManager.getKeyboxFileForUid(callingUid) + val keybox = KeyBoxManager.getAttestationKey(keyboxFile, algorithmName) ?: return null + + val keyboxCertChainBytes = keybox.certificates + .map { it.encoded } + .fold(ByteArray(0)) { acc, der -> acc + der } + + val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel) + val config = CertGenConfig( + algorithm = params.algorithm, + keySize = params.keySize, + ecCurve = params.ecCurve ?: 0, + rsaPublicExponent = params.rsaPublicExponent?.toLong() ?: 65537L, + attestationChallenge = params.attestationChallenge, + purposes = params.purpose.toIntArray(), + digests = params.digest.toIntArray(), + certSerial = params.certificateSerial?.toByteArray(), + certSubject = params.certificateSubject?.encoded, + certNotBefore = params.certificateNotBefore?.time ?: -1L, + certNotAfter = params.certificateNotAfter?.time ?: -1L, + keyboxPrivateKey = keybox.keyPair.private.encoded, + keyboxCertChain = keyboxCertChainBytes, + securityLevel = securityLevel, + attestVersion = attestVersion, + keymasterVersion = AndroidDeviceUtils.getKeymasterVersion(securityLevel), + osVersion = AndroidDeviceUtils.osVersion, + osPatchLevel = AndroidDeviceUtils.getPatchLevel(callingUid), + vendorPatchLevel = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid), + bootPatchLevel = AndroidDeviceUtils.getBootPatchLevelLong(callingUid), + bootKey = AndroidDeviceUtils.bootKey, + bootHash = AndroidDeviceUtils.bootHash, + creationDatetime = System.currentTimeMillis(), + attestationApplicationId = AttestationBuilder.createApplicationId(callingUid).octets, + moduleHash = if (attestVersion >= 400) AndroidDeviceUtils.moduleHash else null, + idBrand = params.brand, + idDevice = params.device, + idProduct = params.product, + idSerial = params.serial, + idImei = params.imei, + idMeid = params.meid, + idManufacturer = params.manufacturer, + idModel = params.model, + idSecondImei = if (attestVersion >= 300) params.secondImei else null, + activeDatetime = params.activeDateTime?.time ?: -1L, + originationExpireDatetime = params.originationExpireDateTime?.time ?: -1L, + usageExpireDatetime = params.usageExpireDateTime?.time ?: -1L, + usageCountLimit = params.usageCountLimit ?: -1, + callerNonce = params.callerNonce == true, + unlockedDeviceRequired = params.unlockedDeviceRequired == true, + noAuthRequired = params.noAuthRequired != false, + ) + + val resultBytes = NativeCertGen.generateAttestedKeyPair(config) ?: return null + val (keyPair, certs) = NativeCertGen.parseNativeResult(resultBytes) + SystemLogger.info("NativeCertGen: ${certs.size} certs generated") + AndroidPair(keyPair, certs) + }.onFailure { + SystemLogger.error("NativeCertGen failed, falling back to BouncyCastle", it) + }.getOrNull() + } + + fun loadPersistedKeys() { + val records = GeneratedKeyPersistence.loadAll(securityLevel) + if (records.isEmpty()) return + + SystemLogger.info("Restoring ${records.size} persisted keys for security level $securityLevel") + for (record in records) { + runCatching { + val keyId = KeyIdentifier(record.uid, record.alias) + if (generatedKeys.containsKey(keyId)) return@runCatching + + val algorithmName = when (record.algorithm) { + Algorithm.EC -> "EC" + Algorithm.RSA -> "RSA" + else -> throw IllegalArgumentException("Unknown algorithm: ${record.algorithm}") + } + + val keyFactory = KeyFactory.getInstance(algorithmName) + val privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(record.privateKeyBytes)) + + val certFactory = CertificateFactory.getInstance("X.509") + val certChain = record.certChainBytes.map { bytes -> + certFactory.generateCertificate(ByteArrayInputStream(bytes)) + } + require(certChain.isNotEmpty()) { "Empty certificate chain" } + + val keyPair = KeyPair(certChain[0].publicKey, privateKey) + val descriptor = KeyDescriptor().apply { + domain = Domain.APP + nspace = record.nspace + alias = record.alias + blob = null + } + + val attestation = KeyMintAttestation( + algorithm = record.algorithm, + ecCurve = if (record.algorithm == Algorithm.EC) record.ecCurve else null, + ecCurveName = "", + keySize = record.keySize, + origin = null, + noAuthRequired = null, + blockMode = emptyList(), + padding = emptyList(), + purpose = record.purposes, + digest = record.digests, + rsaPublicExponent = null, + certificateSerial = null, + certificateSubject = null, + certificateNotBefore = null, + certificateNotAfter = null, + attestationChallenge = null, + brand = null, device = null, product = null, serial = null, + imei = null, meid = null, manufacturer = null, model = null, + secondImei = null, + activeDateTime = null, + originationExpireDateTime = null, + usageExpireDateTime = null, + usageCountLimit = null, + callerNonce = null, + unlockedDeviceRequired = null, + includeUniqueId = null, + rollbackResistance = null, + earlyBootOnly = null, + allowWhileOnBody = null, + trustedUserPresenceRequired = null, + trustedConfirmationRequired = null, + maxUsesPerBoot = null, + maxBootLevel = null, + minMacLength = null, + rsaOaepMgfDigest = emptyList(), + ) + + val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor) + generatedKeys[keyId] = GeneratedKeyInfo(keyPair, null, record.nspace, response, attestation) + if (record.isAttestationKey) attestationKeys.add(keyId) + SystemLogger.debug("Restored persisted key: $keyId") + }.onFailure { + SystemLogger.error("Failed to restore key: uid=${record.uid} alias=${record.alias}", it) + } + } + SystemLogger.info("Key restoration complete. Total in memory: ${generatedKeys.size}") + } + /** * Races TEE hardware generation against software generation concurrently for AUTO mode. * If TEE succeeds, the software future is cancelled and TEE is marked functional. @@ -690,8 +874,8 @@ class KeyMintSecurityLevelInterceptor( companion object { private val secureRandom = SecureRandom() + private val persistExecutor = Executors.newSingleThreadExecutor() - /** Once set to true, AUTO mode skips the race and uses PATCH directly. */ @Volatile var teeFunctional = false private const val INVALID_ARGUMENT = 20 @@ -770,6 +954,7 @@ class KeyMintSecurityLevelInterceptor( fun cleanupKeyData(keyId: KeyIdentifier) { if (generatedKeys.remove(keyId) != null) { SystemLogger.debug("Remove generated key ${keyId}") + GeneratedKeyPersistence.delete(keyId) } if (patchedChains.remove(keyId) != null) { SystemLogger.debug("Remove patched chain for ${keyId}") @@ -801,6 +986,7 @@ class KeyMintSecurityLevelInterceptor( importedKeys.clear() usageCounters.clear() teeResponses.clear() + GeneratedKeyPersistence.deleteAll() SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.") } }