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:
@@ -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!")
|
||||||
|
|||||||
+84
-3
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user