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) + } +}