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.
This commit is contained in:
Enginex0
2026-03-09 16:23:09 +01:00
parent c87759c6c3
commit ac9641ed2a
4 changed files with 194 additions and 4 deletions
@@ -13,6 +13,7 @@ import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.NativeCertGen
import org.matrix.TEESimulator.util.AndroidDeviceUtils import org.matrix.TEESimulator.util.AndroidDeviceUtils
/** /**
@@ -51,6 +52,8 @@ object App {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider()) Security.addProvider(BouncyCastleProvider())
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
// This starts the message queue processing. It blocks here indefinitely // This starts the message queue processing. It blocks here indefinitely
// processing messages until Looper.myLooper().quit() is called. // processing messages until Looper.myLooper().quit() is called.
Looper.loop() Looper.loop()
@@ -345,7 +345,7 @@ object AttestationBuilder {
* retrieved. * retrieved.
*/ */
@Throws(Throwable::class) @Throws(Throwable::class)
private fun createApplicationId(uid: Int): DEROctetString { internal fun createApplicationId(uid: Int): DEROctetString {
val pm = val pm =
ConfigurationManager.getPackageManager() ConfigurationManager.getPackageManager()
?: throw IllegalStateException("PackageManager not found!") ?: throw IllegalStateException("PackageManager not found!")
@@ -8,6 +8,7 @@ import android.hardware.security.keymint.Tag
import android.os.IBinder import android.os.IBinder
import android.os.Parcel import android.os.Parcel
import android.system.keystore2.* import android.system.keystore2.*
import android.util.Pair as AndroidPair
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.security.KeyFactory import java.security.KeyFactory
import java.security.KeyPair import java.security.KeyPair
@@ -17,6 +18,7 @@ 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.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager 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.InterceptorUtils
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertGenConfig
import org.matrix.TEESimulator.pki.CertificateGenerator import org.matrix.TEESimulator.pki.CertificateGenerator
import org.matrix.TEESimulator.pki.CertificateHelper 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( class KeyMintSecurityLevelInterceptor(
private val original: IKeystoreSecurityLevel, private val original: IKeystoreSecurityLevel,
@@ -296,9 +302,16 @@ class KeyMintSecurityLevelInterceptor(
keyDescriptor.nspace = secureRandom.nextLong() keyDescriptor.nspace = secureRandom.nextLong()
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].") SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
val keyData = CertificateGenerator.generateAttestedKeyPair( val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
callingUid, keyDescriptor.alias, attestationKey?.alias, parsedParams, securityLevel, generateAttestedKeyPairNative(callingUid, parsedParams)
) ?: throw Exception("CertificateGenerator failed to create key pair.") ?: 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) cleanupKeyData(keyId)
val response = buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor) val response = buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
@@ -322,6 +335,74 @@ class KeyMintSecurityLevelInterceptor(
return InterceptorUtils.createTypedObjectReply(response.metadata) return InterceptorUtils.createTypedObjectReply(response.metadata)
} }
private fun generateAttestedKeyPairNative(
callingUid: Int,
params: KeyMintAttestation,
): AndroidPair<KeyPair, List<Certificate>>? {
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( private fun buildKeyEntryResponse(
chain: List<Certificate>, chain: List<Certificate>,
params: KeyMintAttestation, params: KeyMintAttestation,
@@ -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<KeyPair, List<Certificate>> {
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<Certificate>()
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)
}
}