From ac9641ed2aa1121aa5ee1efe8aff101897b96c3d Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Mon, 9 Mar 2026 16:23:09 +0100 Subject: [PATCH] feat(pki): integrate native cert gen with BouncyCastle fallback NativeCertGen.kt provides CertGenConfig data class and JNI bridge to libcertgen.so. KeyMintSecurityLevelInterceptor.doSoftwareKeyGen() tries native path first, falls back to BouncyCastle on failure or when library unavailable. App.kt loads libcertgen.so at daemon start. --- .../main/java/org/matrix/TEESimulator/App.kt | 3 + .../attestation/AttestationBuilder.kt | 2 +- .../shim/KeyMintSecurityLevelInterceptor.kt | 87 +++++++++++++- .../matrix/TEESimulator/pki/NativeCertGen.kt | 106 ++++++++++++++++++ 4 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt diff --git a/app/src/main/java/org/matrix/TEESimulator/App.kt b/app/src/main/java/org/matrix/TEESimulator/App.kt index de71061..b51aaf1 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 /** @@ -51,6 +52,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 06027cd..df34b85 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt @@ -345,7 +345,7 @@ object AttestationBuilder { * retrieved. */ @Throws(Throwable::class) - private fun createApplicationId(uid: Int): DEROctetString { + internal fun createApplicationId(uid: Int): DEROctetString { val pm = ConfigurationManager.getPackageManager() ?: throw IllegalStateException("PackageManager not found!") 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 cce3a90..9c1bcf7 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 @@ -8,6 +8,7 @@ import android.hardware.security.keymint.Tag import android.os.IBinder import android.os.Parcel import android.system.keystore2.* +import android.util.Pair as AndroidPair import java.io.ByteArrayInputStream import java.security.KeyFactory import java.security.KeyPair @@ -17,6 +18,7 @@ import java.security.cert.CertificateFactory import java.security.spec.PKCS8EncodedKeySpec import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger +import org.matrix.TEESimulator.attestation.AttestationBuilder import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.config.ConfigurationManager @@ -24,8 +26,12 @@ 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 class KeyMintSecurityLevelInterceptor( private val original: IKeystoreSecurityLevel, @@ -296,9 +302,16 @@ class KeyMintSecurityLevelInterceptor( keyDescriptor.nspace = secureRandom.nextLong() SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].") - val keyData = CertificateGenerator.generateAttestedKeyPair( - callingUid, keyDescriptor.alias, attestationKey?.alias, parsedParams, securityLevel, - ) ?: throw Exception("CertificateGenerator failed to create key pair.") + 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("Both native and BouncyCastle cert gen failed.") cleanupKeyData(keyId) val response = buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor) @@ -322,6 +335,74 @@ class KeyMintSecurityLevelInterceptor( 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 keyboxPrivateKeyBytes = keybox.keyPair.private.encoded + val keyboxCertChainBytes = keybox.certificates + .map { it.encoded } + .fold(ByteArray(0)) { acc, der -> acc + der } + + val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel) + val keymasterVersion = AndroidDeviceUtils.getKeymasterVersion(securityLevel) + val appId = AttestationBuilder.createApplicationId(callingUid) + + val config = CertGenConfig( + algorithm = params.algorithm, + keySize = params.keySize, + ecCurve = params.ecCurve, + 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 = keyboxPrivateKeyBytes, + keyboxCertChain = keyboxCertChainBytes, + securityLevel = securityLevel, + attestVersion = attestVersion, + keymasterVersion = keymasterVersion, + 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 = appId.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, + ) + + val resultBytes = NativeCertGen.generateAttestedKeyPair(config) ?: return null + val (keyPair, certs) = NativeCertGen.parseNativeResult(resultBytes) + SystemLogger.info("NativeCertGen: generated key pair successfully (${certs.size} certs)") + AndroidPair(keyPair, certs) + }.onFailure { + SystemLogger.error("NativeCertGen: generation failed, falling back to BouncyCastle", it) + }.getOrNull() + } + private fun buildKeyEntryResponse( chain: List, params: KeyMintAttestation, diff --git a/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt b/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt new file mode 100644 index 0000000..c4cafe8 --- /dev/null +++ b/app/src/main/java/org/matrix/TEESimulator/pki/NativeCertGen.kt @@ -0,0 +1,106 @@ +package org.matrix.TEESimulator.pki + +import java.io.ByteArrayInputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.security.KeyFactory +import java.security.KeyPair +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.spec.PKCS8EncodedKeySpec +import org.matrix.TEESimulator.logging.SystemLogger + +data class CertGenConfig( + val algorithm: Int, + val keySize: Int, + val ecCurve: Int, + val rsaPublicExponent: Long, + val attestationChallenge: ByteArray?, + val purposes: IntArray, + val digests: IntArray, + val certSerial: ByteArray?, + val certSubject: ByteArray?, + val certNotBefore: Long, + val certNotAfter: Long, + val keyboxPrivateKey: ByteArray, + val keyboxCertChain: ByteArray, + val securityLevel: Int, + val attestVersion: Int, + val keymasterVersion: Int, + val osVersion: Int, + val osPatchLevel: Int, + val vendorPatchLevel: Int, + val bootPatchLevel: Int, + val bootKey: ByteArray, + val bootHash: ByteArray, + val creationDatetime: Long, + val attestationApplicationId: ByteArray, + val moduleHash: ByteArray?, + val idBrand: ByteArray?, + val idDevice: ByteArray?, + val idProduct: ByteArray?, + val idSerial: ByteArray?, + val idImei: ByteArray?, + val idMeid: ByteArray?, + val idManufacturer: ByteArray?, + val idModel: ByteArray?, + val idSecondImei: ByteArray?, +) + +object NativeCertGen { + + @Volatile + var isAvailable: Boolean = false + private set + + fun initialize(libraryPath: String) { + try { + System.load(libraryPath) + isAvailable = true + SystemLogger.info("NativeCertGen: loaded libcertgen.so successfully") + } catch (e: UnsatisfiedLinkError) { + SystemLogger.error("NativeCertGen: failed to load libcertgen.so, falling back to BouncyCastle", e) + } + } + + external fun generateAttestedKeyPair(config: CertGenConfig): ByteArray? + + external fun generateSoftwareKeyPair( + algorithm: Int, + keySize: Int, + ecCurve: Int, + rsaPublicExponent: Long, + ): ByteArray? + + external fun initLogging(verbose: Boolean) + + external fun dumpLogs(): String + + fun parseNativeResult(bytes: ByteArray): Pair> { + val buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN) + + val pkLen = buf.getInt() + val pkBytes = ByteArray(pkLen) + buf.get(pkBytes) + + val numCerts = buf.getInt() + val certs = mutableListOf() + val certFactory = CertificateFactory.getInstance("X.509") + repeat(numCerts) { + val certLen = buf.getInt() + val certBytes = ByteArray(certLen) + buf.get(certBytes) + certs.add(certFactory.generateCertificate(ByteArrayInputStream(certBytes))) + } + + val algorithmName = when (certs[0].publicKey.algorithm) { + "EC" -> "EC" + "RSA" -> "RSA" + else -> certs[0].publicKey.algorithm + } + val keyFactory = KeyFactory.getInstance(algorithmName) + val privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(pkBytes)) + val publicKey = certs[0].publicKey + return Pair(KeyPair(publicKey, privateKey), certs) + } +}