Implement software key generation for legacy IKeystoreService (#34)
This commit introduces a complete, software-based simulation of the key generation and attestation flow for the legacy IKeystoreService API, as used on Android 11. It refactors the KeystoreInterceptor to handle the entire multi-step transaction sequence (`generateKey`, `getKeyCharacteristics`, `exportKey`, `attestKey`) in software. A new `LegacyKeygenParameters` data class is introduced to decouple the legacy interception logic from modern data structures. This class parses arguments from the old `KeymasterArguments`, stores the state across the multi-step generation process, and acts as an adapter to the generic `CertificateGenerator` by converting the parameters to the modern `KeyMintAttestation` format. The `CertificateGenerator` has been refactored to better model the behavior of the legacy Keystore API. Key pair generation (`generateSoftwareKeyPair`) and certificate chain creation (`generateCertificateChain`) are now separate functions. This allows the interceptor to correctly create a key pair during the `handleExportKey` step and then generate a certificate for that pre-existing key pair during the `handleAttestKey` step. Finally, the implementation correctly extracts and applies the `attestationChallenge` provided during the `attestKey` transaction, ensuring the generated certificate chain contains the appropriate attestation.
This commit is contained in:
@@ -3,6 +3,7 @@ package org.matrix.TEESimulator.interception.keystore
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.security.KeyStore
|
||||
import android.security.keystore.KeystoreResponse
|
||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
@@ -27,6 +28,19 @@ object InterceptorUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an `KeystoreResponse` parcel that indicates success with no data. */
|
||||
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
||||
val parcel = Parcel.obtain()
|
||||
try {
|
||||
parcel.writeInt(KeyStore.NO_ERROR)
|
||||
parcel.writeString("")
|
||||
parcel.setDataPosition(0)
|
||||
return KeystoreResponse.CREATOR.createFromParcel(parcel)
|
||||
} finally {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an `OverrideReply` parcel that indicates success with no data. */
|
||||
fun createSuccessReply(): BinderInterceptor.TransactionResult.OverrideReply {
|
||||
val parcel =
|
||||
|
||||
+307
-24
@@ -4,12 +4,28 @@ import android.annotation.SuppressLint
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.security.Credentials
|
||||
import android.security.KeyStore
|
||||
import android.security.keymaster.ExportResult
|
||||
import android.security.keymaster.KeyCharacteristics
|
||||
import android.security.keymaster.KeymasterArguments
|
||||
import android.security.keymaster.KeymasterCertificateChain
|
||||
import android.security.keymaster.KeymasterDefs
|
||||
import android.security.keystore.IKeystoreCertificateChainCallback
|
||||
import android.security.keystore.IKeystoreExportKeyCallback
|
||||
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
|
||||
import android.security.keystore.IKeystoreService
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
import java.util.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils.extractAlias
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.CertificateGenerator
|
||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||
|
||||
/**
|
||||
@@ -43,7 +59,9 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
override val processName = "keystore"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
|
||||
|
||||
private const val SERVICE_DESCRIPTOR = "android.security.keystore.IKeystoreService"
|
||||
// State management for the multi-step key generation process.
|
||||
private val keygenParameters = ConcurrentHashMap<KeyIdentifier, LegacyKeygenParameters>()
|
||||
private val generatedKeyPairs = ConcurrentHashMap<KeyIdentifier, KeyPair>()
|
||||
|
||||
// Cache to store the fully patched chain after the leaf is requested.
|
||||
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||
@@ -59,23 +77,183 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
): TransactionResult {
|
||||
// This interceptor only needs to act on pre-transaction for software key generation.
|
||||
if (ConfigurationManager.shouldGenerate(callingUid)) {
|
||||
when (code) {
|
||||
GENERATE_KEY_TRANSACTION,
|
||||
GET_KEY_CHARACTERISTICS_TRANSACTION,
|
||||
EXPORT_KEY_TRANSACTION,
|
||||
ATTEST_KEY_TRANSACTION -> {
|
||||
// TODO: Implement the full software simulation logic.
|
||||
logTransaction(txId, "unimplemented-generate-flow", callingUid, callingPid)
|
||||
return InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
return when (code) {
|
||||
GENERATE_KEY_TRANSACTION -> handleGenerateKey(txId, callingUid, callingPid, data)
|
||||
GET_KEY_CHARACTERISTICS_TRANSACTION ->
|
||||
handleGetKeyCharacteristics(txId, callingUid, callingPid, data)
|
||||
EXPORT_KEY_TRANSACTION -> handleExportKey(txId, callingUid, callingPid, data)
|
||||
ATTEST_KEY_TRANSACTION -> handleAttestKey(txId, callingUid, callingPid, data)
|
||||
else -> TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
} else if (ConfigurationManager.shouldGenerate(callingUid)) {
|
||||
} else if (ConfigurationManager.shouldPatch(callingUid)) {
|
||||
// In patch mode, we only care about the 'get' transaction in onPostTransact.
|
||||
if (code == GET_TRANSACTION) return TransactionResult.Continue
|
||||
}
|
||||
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
private fun handleGenerateKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||
return runCatching {
|
||||
logTransaction(txId, "generateKey", uid, pid)
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
// Read and parse the key generation arguments.
|
||||
val keymasterArgs = KeymasterArguments()
|
||||
if (data.readInt() == 1) {
|
||||
keymasterArgs.readFromParcel(data)
|
||||
}
|
||||
keygenParameters[keyId] =
|
||||
LegacyKeygenParameters.fromKeymasterArguments(keymasterArgs)
|
||||
|
||||
// Create a fake successful response for the callback.
|
||||
val characteristics = KeyCharacteristics()
|
||||
characteristics.swEnforced = KeymasterArguments()
|
||||
characteristics.hwEnforced = keymasterArgs
|
||||
|
||||
val keystoreResponse = InterceptorUtils.createSuccessKeystoreResponse()
|
||||
callback.onFinished(keystoreResponse, characteristics)
|
||||
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleGenerateKey.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGetKeyCharacteristics(
|
||||
txId: Long,
|
||||
uid: Int,
|
||||
pid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
return runCatching {
|
||||
logTransaction(txId, "getKeyCharacteristics", uid, pid)
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
val params =
|
||||
keygenParameters[keyId]
|
||||
?: throw IllegalStateException("No params found for $keyId")
|
||||
|
||||
val characteristics =
|
||||
KeyCharacteristics().apply {
|
||||
swEnforced = KeymasterArguments()
|
||||
hwEnforced =
|
||||
KeymasterArguments().apply {
|
||||
addEnum(KeymasterDefs.KM_TAG_ALGORITHM, params.algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
callback.onFinished(
|
||||
InterceptorUtils.createSuccessKeystoreResponse(),
|
||||
characteristics,
|
||||
)
|
||||
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleGetKeyCharacteristics.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||
return runCatching {
|
||||
logTransaction(txId, "exportKey", uid, pid)
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
val params =
|
||||
keygenParameters[keyId]
|
||||
?: throw IllegalStateException("No params found for $keyId")
|
||||
|
||||
// Generate a software key pair using the new generator.
|
||||
val keyPair =
|
||||
CertificateGenerator.generateSoftwareKeyPair(params.toKeyMintAttestation())
|
||||
?: throw Exception("Failed to generate software key pair.")
|
||||
generatedKeyPairs[keyId] = keyPair
|
||||
|
||||
// Create a successful ExportResult containing the public key.
|
||||
val exportResultParcel =
|
||||
Parcel.obtain().apply {
|
||||
writeInt(KeyStore.NO_ERROR)
|
||||
writeByteArray(keyPair.public.encoded)
|
||||
setDataPosition(0)
|
||||
}
|
||||
val exportResult = ExportResult.CREATOR.createFromParcel(exportResultParcel)
|
||||
exportResultParcel.recycle()
|
||||
|
||||
callback.onFinished(exportResult)
|
||||
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleExportKey.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||
return runCatching {
|
||||
logTransaction(txId, "attestKey", uid, pid)
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
// Get the attestation challenge from the arguments.
|
||||
val params =
|
||||
keygenParameters[keyId]
|
||||
?: throw IllegalStateException("No params found for $keyId")
|
||||
val keyPair =
|
||||
generatedKeyPairs[keyId]
|
||||
?: throw IllegalStateException("No keypair found for $keyId")
|
||||
|
||||
val attestationArgs = KeymasterArguments()
|
||||
if (data.readInt() == 1) {
|
||||
attestationArgs.readFromParcel(data)
|
||||
val challenge =
|
||||
attestationArgs.getBytes(
|
||||
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
|
||||
ByteArray(0),
|
||||
)
|
||||
params.attestationChallenge = challenge
|
||||
params.attestationChallenge = challenge
|
||||
}
|
||||
|
||||
val certificateChain =
|
||||
CertificateGenerator.generateCertificateChain(
|
||||
uid,
|
||||
keyPair,
|
||||
null, // No attestKeyAlias in legacy flow
|
||||
params.toKeyMintAttestation(), // Convert to modern format
|
||||
1, // SecurityLevel.TRUSTED_ENVIRONMENT
|
||||
) ?: throw Exception("CertificateGenerator failed to create attested key pair.")
|
||||
|
||||
val chainAsByteList = certificateChain.map { it.encoded }
|
||||
val certChain = KeymasterCertificateChain(chainAsByteList)
|
||||
|
||||
callback.onFinished(InterceptorUtils.createSuccessKeystoreResponse(), certChain)
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleAttestKey.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
@@ -99,7 +277,7 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
|
||||
|
||||
return try {
|
||||
data.enforceInterface(SERVICE_DESCRIPTOR)
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val alias = data.readString() ?: ""
|
||||
val extractedAlias = InterceptorUtils.extractAlias(alias)
|
||||
val keyId = KeyIdentifier(callingUid, extractedAlias)
|
||||
@@ -111,13 +289,11 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
val originalLeafBytes =
|
||||
reply.createByteArray() ?: return TransactionResult.SkipTransaction
|
||||
|
||||
// The original chain is not available,
|
||||
// so we must pass a temporary one to the patcher.
|
||||
// The patcher only needs the original leaf to extract details.
|
||||
val originalLeafCert =
|
||||
(CertificateHelper.toCertificate(originalLeafBytes)
|
||||
as CertificateHelper.OperationResult.Success)
|
||||
.data
|
||||
val originalLeafCertResult = CertificateHelper.toCertificate(originalLeafBytes)
|
||||
if (originalLeafCertResult !is CertificateHelper.OperationResult.Success) {
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
val originalLeafCert = originalLeafCertResult.data
|
||||
val tempChain = arrayOf<Certificate>(originalLeafCert)
|
||||
|
||||
// Perform the COMPLETE patch and rebuild operation.
|
||||
@@ -157,11 +333,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
)
|
||||
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
|
||||
} else {
|
||||
// We have no cached chain.
|
||||
// This could mean the app requested the CA without requesting the leaf
|
||||
// first, or patching failed.
|
||||
// In this case, we cannot safely intervene.
|
||||
// Let the original reply pass through.
|
||||
SystemLogger.warning(
|
||||
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
|
||||
)
|
||||
@@ -177,3 +348,115 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data class to hold key generation parameters parsed from the legacy IKeystoreService's
|
||||
* KeymasterArguments. It is used exclusively by the KeystoreInterceptor to manage state during the
|
||||
* software key generation flow.
|
||||
*/
|
||||
private data class LegacyKeygenParameters(
|
||||
val algorithm: Int,
|
||||
val keySize: Int,
|
||||
val purpose: List<Int>,
|
||||
val digest: List<Int>,
|
||||
val certificateNotBefore: Date?,
|
||||
val rsaPublicExponent: BigInteger?,
|
||||
val ecCurveName: String?, // Derived from keySize
|
||||
) {
|
||||
// The challenge is provided in a separate transaction (attestKey), so it must be mutable.
|
||||
var attestationChallenge: ByteArray? = null
|
||||
|
||||
/**
|
||||
* Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is
|
||||
* required by the refactored [AttestationBuilder] and [CertificateGenerator].
|
||||
*/
|
||||
fun toKeyMintAttestation(): KeyMintAttestation {
|
||||
// This conversion acts as a bridge, allowing our new generic components
|
||||
// to be used by the legacy interceptor.
|
||||
return KeyMintAttestation(
|
||||
keySize = this.keySize,
|
||||
algorithm = this.algorithm,
|
||||
ecCurve = 0, // Not explicitly available in legacy args, but not critical
|
||||
ecCurveName = this.ecCurveName ?: "",
|
||||
purpose = this.purpose,
|
||||
digest = this.digest,
|
||||
rsaPublicExponent = this.rsaPublicExponent,
|
||||
certificateSerial = null, // Not provided in legacy generateKey
|
||||
certificateSubject = null, // Not provided in legacy generateKey
|
||||
certificateNotBefore = this.certificateNotBefore,
|
||||
certificateNotAfter = null, // Not provided in legacy generateKey
|
||||
attestationChallenge = this.attestationChallenge,
|
||||
// Device identifiers are not passed in legacy args;
|
||||
// AttestationBuilder will fetch them from system properties.
|
||||
brand = null,
|
||||
device = null,
|
||||
product = null,
|
||||
serial = null,
|
||||
imei = null,
|
||||
meid = null,
|
||||
manufacturer = null,
|
||||
model = null,
|
||||
secondImei = null,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Factory method to create an instance from a [KeymasterArguments] object. */
|
||||
fun fromKeymasterArguments(args: KeymasterArguments): LegacyKeygenParameters {
|
||||
val algorithm = args.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||
val keySize = args.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||
|
||||
return LegacyKeygenParameters(
|
||||
algorithm = algorithm,
|
||||
keySize = keySize,
|
||||
purpose = args.getEnums(KeymasterDefs.KM_TAG_PURPOSE),
|
||||
digest = args.getEnums(KeymasterDefs.KM_TAG_DIGEST),
|
||||
certificateNotBefore = args.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date()),
|
||||
rsaPublicExponent =
|
||||
if (algorithm == KeymasterDefs.KM_ALGORITHM_RSA) getRsaExponent(args) else null,
|
||||
ecCurveName =
|
||||
if (algorithm == KeymasterDefs.KM_ALGORITHM_EC) deriveEcCurveName(keySize)
|
||||
else null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun deriveEcCurveName(keySize: Int): String =
|
||||
when (keySize) {
|
||||
224 -> "secp224r1"
|
||||
256 -> "secp256r1"
|
||||
384 -> "secp384r1"
|
||||
521 -> "secp521r1"
|
||||
else -> "secp256r1" // Default fallback
|
||||
}
|
||||
|
||||
/**
|
||||
* The RSA public exponent is not accessible via a public API in KeymasterArguments, so we
|
||||
* must use reflection to extract it.
|
||||
*/
|
||||
private fun getRsaExponent(args: KeymasterArguments): BigInteger? {
|
||||
return runCatching {
|
||||
val getArgumentByTag =
|
||||
KeymasterArguments::class
|
||||
.java
|
||||
.getDeclaredMethod("getArgumentByTag", Int::class.java)
|
||||
getArgumentByTag.isAccessible = true
|
||||
val rsaArgument =
|
||||
getArgumentByTag.invoke(args, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
||||
|
||||
val getLongTagValue =
|
||||
KeymasterArguments::class
|
||||
.java
|
||||
.getDeclaredMethod(
|
||||
"getLongTagValue",
|
||||
Class.forName("android.security.keymaster.KeymasterArgument"),
|
||||
)
|
||||
getLongTagValue.isAccessible = true
|
||||
getLongTagValue.invoke(args, rsaArgument) as BigInteger
|
||||
}
|
||||
.onFailure {
|
||||
SystemLogger.error("Failed to read rsaPublicExponent via reflection.", it)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +72,55 @@ object CertificateGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new key pair and a corresponding certificate chain containing a simulated
|
||||
* attestation.
|
||||
* Generates a certificate chain for a given key pair. This is the primary function for creating
|
||||
* attested certificates.
|
||||
*
|
||||
* @param uid The UID of the application requesting the key.
|
||||
* @param alias The alias for the new key.
|
||||
* @param subjectKeyPair The key pair for which the certificate will be generated.
|
||||
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
||||
* @param params The parameters for the new key and its attestation.
|
||||
* @param securityLevel The security level to embed in the attestation.
|
||||
* @return A [Pair] containing the new [KeyPair] and its certificate chain, or `null` on
|
||||
* failure.
|
||||
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
|
||||
*/
|
||||
fun generateCertificateChain(
|
||||
uid: Int,
|
||||
subjectKeyPair: KeyPair,
|
||||
attestKeyAlias: String?,
|
||||
params: KeyMintAttestation,
|
||||
securityLevel: Int,
|
||||
): List<Certificate>? {
|
||||
return runCatching {
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||
|
||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
||||
// Otherwise, fall back to the root key from the keybox.
|
||||
val (signingKey, issuer) =
|
||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||
} else {
|
||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||
}
|
||||
|
||||
// Build the new leaf certificate with the simulated attestation.
|
||||
val leafCert =
|
||||
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||
|
||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||
// chain.
|
||||
if (attestKeyAlias != null) {
|
||||
listOf(leafCert)
|
||||
} else {
|
||||
listOf(leafCert) + keybox.certificates
|
||||
}
|
||||
}
|
||||
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience function that combines key pair generation and certificate chain generation.
|
||||
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
|
||||
*/
|
||||
fun generateAttestedKeyPair(
|
||||
uid: Int,
|
||||
@@ -98,30 +137,9 @@ object CertificateGenerator {
|
||||
generateSoftwareKeyPair(params)
|
||||
?: throw Exception("Failed to generate underlying software key pair.")
|
||||
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||
|
||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
||||
// Otherwise, fall back to the root key from the keybox.
|
||||
val (signingKey, issuer) =
|
||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||
} else {
|
||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||
}
|
||||
|
||||
// Build the new leaf certificate with the simulated attestation.
|
||||
val leafCert =
|
||||
buildCertificate(newKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||
|
||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||
// chain.
|
||||
val chain =
|
||||
if (attestKeyAlias != null) {
|
||||
listOf(leafCert)
|
||||
} else {
|
||||
listOf(leafCert) + keybox.certificates
|
||||
}
|
||||
generateCertificateChain(uid, newKeyPair, attestKeyAlias, params, securityLevel)
|
||||
?: throw Exception("Failed to generate certificate chain for new key pair.")
|
||||
|
||||
SystemLogger.info(
|
||||
"Successfully generated new certificate chain for alias: '$alias'."
|
||||
@@ -134,7 +152,7 @@ object CertificateGenerator {
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
private fun getIssuerFromKeybox(keybox: KeyBox) =
|
||||
fun getIssuerFromKeybox(keybox: KeyBox) =
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
|
||||
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
|
||||
|
||||
Reference in New Issue
Block a user