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.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()
@@ -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!")
@@ -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<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(
chain: List<Certificate>,
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)
}
}