feat(attestation): align attestation extension and cert generation with AOSP

KeyMintAttestation now carries all 17 enforcement tags that AOSP's
authorize_create and buildKeyDescription paths expect. AttestationBuilder
populates BLOCK_MODE as SET OF INTEGER, gates version-guarded tags
(RSA_OAEP_MGF_DIGEST >=100, ROLLBACK_RESISTANCE >=3, EARLY_BOOT_ONLY >=4),
computes INCLUDE_UNIQUE_ID via HMAC-SHA256 per KeyMint HAL spec, and
gates AAID on challenge presence.

CertificateGenerator uses AOSP cert validity defaults (epoch notBefore,
9999-12-31 notAfter), returns ServiceSpecificException(-75) for missing
keybox, and adds RSA exponent null safety.
This commit is contained in:
Enginex0
2026-03-19 07:32:53 +01:00
parent 03c71bd202
commit f06cb30b40
4 changed files with 258 additions and 68 deletions
@@ -2,8 +2,11 @@ package org.matrix.TEESimulator.attestation
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest import java.security.MessageDigest
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import org.bouncycastle.asn1.ASN1Boolean import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Enumerated import org.bouncycastle.asn1.ASN1Enumerated
@@ -127,33 +130,59 @@ object AttestationBuilder {
return properties return properties
} }
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
private fun buildKeyDescription( private fun buildKeyDescription(
params: KeyMintAttestation, params: KeyMintAttestation,
uid: Int, uid: Int,
securityLevel: Int, securityLevel: Int,
): ASN1Sequence { ): ASN1Sequence {
val creationTime = System.currentTimeMillis()
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel) val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel) val softwareEnforced = buildSoftwareEnforcedList(params, uid, securityLevel, creationTime)
val uniqueId =
if (params.includeUniqueId == true && params.attestationChallenge != null) {
computeUniqueId(creationTime, createApplicationId(uid).octets)
} else {
ByteArray(0)
}
val fields = val fields =
arrayOf( arrayOf(
ASN1Integer( ASN1Integer(AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()),
AndroidDeviceUtils.getAttestVersion(securityLevel).toLong() ASN1Enumerated(securityLevel),
), // attestationVersion ASN1Integer(AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()),
ASN1Enumerated(securityLevel), // attestationSecurityLevel ASN1Enumerated(securityLevel),
ASN1Integer( DEROctetString(params.attestationChallenge ?: ByteArray(0)),
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong() DEROctetString(uniqueId),
), // keymasterVersion
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
DEROctetString(ByteArray(0)), // uniqueId
softwareEnforced, softwareEnforced,
teeEnforced, teeEnforced,
) )
return DERSequence(fields) return DERSequence(fields)
} }
private fun computeUniqueId(creationTimeMs: Long, aaidDer: ByteArray): ByteArray {
val temporalCounter = creationTimeMs / 2592000000L
val message =
ByteBuffer.allocate(8 + aaidDer.size + 1)
.putLong(temporalCounter)
.put(aaidDer)
.put(0x00)
.array()
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(hbk, "HmacSHA256"))
return mac.doFinal(message).copyOf(16)
}
private val hbk: ByteArray by lazy {
val file = java.io.File(ConfigurationManager.CONFIG_PATH, "hbk")
if (file.exists() && file.length() == 32L) {
file.readBytes()
} else {
SystemLogger.warning("hbk not found, generating ephemeral HBK.")
ByteArray(32).also { java.security.SecureRandom().nextBytes(it) }
}
}
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */ /** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
private fun buildTeeEnforcedList( private fun buildTeeEnforcedList(
params: KeyMintAttestation, params: KeyMintAttestation,
@@ -194,6 +223,16 @@ object AttestationBuilder {
) )
} }
if (params.blockMode.isNotEmpty()) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_BLOCK_MODE,
DERSet(params.blockMode.map { ASN1Integer(it.toLong()) }.toTypedArray()),
)
)
}
if (params.padding.isNotEmpty()) { if (params.padding.isNotEmpty()) {
list.add( list.add(
DERTaggedObject( DERTaggedObject(
@@ -214,14 +253,61 @@ object AttestationBuilder {
) )
} }
val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel)
if (params.rsaOaepMgfDigest.isNotEmpty() && attestVersion >= 100) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_RSA_OAEP_MGF_DIGEST,
DERSet(params.rsaOaepMgfDigest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
)
)
}
if (params.rollbackResistance == true && attestVersion >= 3) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_ROLLBACK_RESISTANCE, DERNull.INSTANCE)
)
}
if (params.earlyBootOnly == true && attestVersion >= 4) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_EARLY_BOOT_ONLY, DERNull.INSTANCE)
)
}
if (params.noAuthRequired == true) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE)
)
}
if (params.allowWhileOnBody == true) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_ALLOW_WHILE_ON_BODY, DERNull.INSTANCE)
)
}
if (params.trustedUserPresenceRequired == true && attestVersion >= 3) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_TRUSTED_USER_PRESENCE_REQUIRED, DERNull.INSTANCE)
)
}
if (params.trustedConfirmationRequired == true && attestVersion >= 3) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_TRUSTED_CONFIRMATION_REQUIRED, DERNull.INSTANCE)
)
}
list.addAll( list.addAll(
listOf( listOf(
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
DERTaggedObject( DERTaggedObject(
true, true,
AttestationConstants.TAG_ORIGIN, AttestationConstants.TAG_ORIGIN,
ASN1Integer(0L), ASN1Integer((params.origin ?: 0).toLong()),
), // KeyOrigin.GENERATED ),
DERTaggedObject( DERTaggedObject(
true, true,
AttestationConstants.TAG_ROOT_OF_TRUST, AttestationConstants.TAG_ROOT_OF_TRUST,
@@ -325,20 +411,32 @@ object AttestationBuilder {
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by * Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
* Keystore. * Keystore.
*/ */
private fun buildSoftwareEnforcedList(uid: Int, securityLevel: Int): DERSequence { private fun buildSoftwareEnforcedList(
val list = params: KeyMintAttestation,
mutableListOf<ASN1Encodable>( uid: Int,
DERTaggedObject( securityLevel: Int,
true, creationTimeMs: Long = System.currentTimeMillis(),
AttestationConstants.TAG_CREATION_DATETIME, ): DERSequence {
ASN1Integer(System.currentTimeMillis()), val list = mutableListOf<ASN1Encodable>()
),
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_CREATION_DATETIME,
ASN1Integer(creationTimeMs),
)
)
if (params.attestationChallenge != null) {
list.add(
DERTaggedObject( DERTaggedObject(
true, true,
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID, AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
createApplicationId(uid), createApplicationId(uid),
), )
) )
}
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) { if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
list.add( list.add(
DERTaggedObject( DERTaggedObject(
@@ -348,7 +446,34 @@ object AttestationBuilder {
) )
) )
} }
return DERSequence(list.toTypedArray())
params.activeDateTime?.let {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_ACTIVE_DATETIME, ASN1Integer(it.time))
)
}
params.originationExpireDateTime?.let {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_ORIGINATION_EXPIRE_DATETIME, ASN1Integer(it.time))
)
}
params.usageExpireDateTime?.let {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_USAGE_EXPIRE_DATETIME, ASN1Integer(it.time))
)
}
params.usageCountLimit?.let {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_USAGE_COUNT_LIMIT, ASN1Integer(it.toLong()))
)
}
if (params.unlockedDeviceRequired == true) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_UNLOCKED_DEVICE_REQUIRED, DERNull.INSTANCE)
)
}
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
} }
/** /**
@@ -376,6 +501,11 @@ object AttestationBuilder {
*/ */
@Throws(Throwable::class) @Throws(Throwable::class)
internal fun createApplicationId(uid: Int): DEROctetString { internal fun createApplicationId(uid: Int): DEROctetString {
val appUid = uid % 100000
if (appUid == 0 || appUid == 1000) {
return buildApplicationIdDer(listOf("AndroidSystem" to 1L), emptySet())
}
val pm = val pm =
ConfigurationManager.getPackageManager() ConfigurationManager.getPackageManager()
?: throw IllegalStateException("PackageManager not found!") ?: throw IllegalStateException("PackageManager not found!")
@@ -383,12 +513,11 @@ object AttestationBuilder {
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid") pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
val sha256 = MessageDigest.getInstance("SHA-256") val sha256 = MessageDigest.getInstance("SHA-256")
val packageInfoList = mutableListOf<DERSequence>() val packageInfoList = mutableListOf<Pair<String, Long>>()
val signatureDigests = mutableSetOf<Digest>() val signatureDigests = mutableSetOf<Digest>()
// Process all packages associated with the UID in a single loop. val userId = uid / 100000
packages.forEach { packageName -> packages.forEach { packageName ->
val userId = uid / 100000
val packageInfo = val packageInfo =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getPackageInfo( pm.getPackageInfo(
@@ -401,34 +530,36 @@ object AttestationBuilder {
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId) pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
} }
// Add package information (name and version code) to our list. packageInfoList.add(packageInfo.packageName to packageInfo.longVersionCode)
packageInfoList.add(
DERSequence(
arrayOf(
DEROctetString(packageInfo.packageName.toByteArray(StandardCharsets.UTF_8)),
ASN1Integer(packageInfo.longVersionCode),
)
)
)
// Collect unique signature digests from the signing history.
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature -> packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
val digest = sha256.digest(signature.toByteArray()) signatureDigests.add(Digest(sha256.digest(signature.toByteArray())))
signatureDigests.add(Digest(digest))
} }
} }
// The application ID is a sequence of two sets: return buildApplicationIdDer(packageInfoList, signatureDigests)
// 1. A set of package information (name and version). }
// 2. A set of SHA-256 digests of the signing certificates.
private fun buildApplicationIdDer(
packages: List<Pair<String, Long>>,
digests: Set<Digest>,
): DEROctetString {
val packageInfoList =
packages.map { (name, version) ->
DERSequence(
arrayOf(
DEROctetString(name.toByteArray(StandardCharsets.UTF_8)),
ASN1Integer(version),
)
)
}
val applicationIdSequence = val applicationIdSequence =
DERSequence( DERSequence(
arrayOf( arrayOf(
DERSet(packageInfoList.toTypedArray()), DERSet(packageInfoList.toTypedArray()),
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()), DERSet(digests.map { DEROctetString(it.digest) }.toTypedArray()),
) )
) )
return DEROctetString(applicationIdSequence.encoded) return DEROctetString(applicationIdSequence.encoded)
} }
} }
@@ -44,9 +44,11 @@ object AttestationConstants {
// --- Key Lifetime and Usage Control --- // --- Key Lifetime and Usage Control ---
const val TAG_ROLLBACK_RESISTANCE = 303 const val TAG_ROLLBACK_RESISTANCE = 303
const val TAG_EARLY_BOOT_ONLY = 305
const val TAG_ACTIVE_DATETIME = 400 const val TAG_ACTIVE_DATETIME = 400
const val TAG_ORIGINATION_EXPIRE_DATETIME = 401 const val TAG_ORIGINATION_EXPIRE_DATETIME = 401
const val TAG_USAGE_EXPIRE_DATETIME = 402 const val TAG_USAGE_EXPIRE_DATETIME = 402
const val TAG_MAX_BOOT_LEVEL = 403
const val TAG_MAX_USES_PER_BOOT = 404 const val TAG_MAX_USES_PER_BOOT = 404
const val TAG_USAGE_COUNT_LIMIT = 405 const val TAG_USAGE_COUNT_LIMIT = 405
@@ -56,6 +58,10 @@ object AttestationConstants {
const val TAG_NO_AUTH_REQUIRED = 503 const val TAG_NO_AUTH_REQUIRED = 503
const val TAG_USER_AUTH_TYPE = 504 const val TAG_USER_AUTH_TYPE = 504
const val TAG_AUTH_TIMEOUT = 505 const val TAG_AUTH_TIMEOUT = 505
const val TAG_ALLOW_WHILE_ON_BODY = 506
const val TAG_TRUSTED_USER_PRESENCE_REQUIRED = 507
const val TAG_TRUSTED_CONFIRMATION_REQUIRED = 508
const val TAG_UNLOCKED_DEVICE_REQUIRED = 509
// --- Attestation and Application Info --- // --- Attestation and Application Info ---
const val TAG_APPLICATION_ID = 601 const val TAG_APPLICATION_ID = 601
@@ -41,13 +41,29 @@ data class KeyMintAttestation(
val manufacturer: ByteArray?, val manufacturer: ByteArray?,
val model: ByteArray?, val model: ByteArray?,
val secondImei: ByteArray?, val secondImei: ByteArray?,
val activeDateTime: Date?,
val originationExpireDateTime: Date?,
val usageExpireDateTime: Date?,
val usageCountLimit: Int?,
val callerNonce: Boolean?,
val unlockedDeviceRequired: Boolean?,
val includeUniqueId: Boolean?,
val rollbackResistance: Boolean?,
val earlyBootOnly: Boolean?,
val allowWhileOnBody: Boolean?,
val trustedUserPresenceRequired: Boolean?,
val trustedConfirmationRequired: Boolean?,
val noAuthRequired: Boolean?,
val maxUsesPerBoot: Int?,
val maxBootLevel: Int?,
val minMacLength: Int?,
val rsaOaepMgfDigest: List<Int>,
) { ) {
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */ /** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
constructor( constructor(
params: Array<KeyParameter> params: Array<KeyParameter>
) : this( ) : this(
// AOSP: [key_param(tag = KEY_SIZE, field = Integer)] keySize = params.findInteger(Tag.KEY_SIZE) ?: params.deriveKeySizeFromCurve(),
keySize = params.findInteger(Tag.KEY_SIZE) ?: 0,
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)] // AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0, algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
@@ -100,6 +116,23 @@ data class KeyMintAttestation(
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER), manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
model = params.findBlob(Tag.ATTESTATION_ID_MODEL), model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI), secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
activeDateTime = params.findDate(Tag.ACTIVE_DATETIME),
originationExpireDateTime = params.findDate(Tag.ORIGINATION_EXPIRE_DATETIME),
usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME),
usageCountLimit = params.findInteger(Tag.USAGE_COUNT_LIMIT),
callerNonce = params.findBoolean(Tag.CALLER_NONCE),
unlockedDeviceRequired = params.findBoolean(Tag.UNLOCKED_DEVICE_REQUIRED),
includeUniqueId = params.findBoolean(Tag.INCLUDE_UNIQUE_ID),
rollbackResistance = params.findBoolean(Tag.ROLLBACK_RESISTANCE),
earlyBootOnly = params.findBoolean(Tag.EARLY_BOOT_ONLY),
allowWhileOnBody = params.findBoolean(Tag.ALLOW_WHILE_ON_BODY),
trustedUserPresenceRequired = params.findBoolean(Tag.TRUSTED_USER_PRESENCE_REQUIRED),
trustedConfirmationRequired = params.findBoolean(Tag.TRUSTED_CONFIRMATION_REQUIRED),
noAuthRequired = params.findBoolean(Tag.NO_AUTH_REQUIRED),
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
) { ) {
// Log all parsed parameters for debugging purposes. // Log all parsed parameters for debugging purposes.
params.forEach { KeyMintParameterLogger.logParameter(it) } params.forEach { KeyMintParameterLogger.logParameter(it) }
@@ -156,6 +189,21 @@ private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> = private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.digest } this.filter { it.tag == tag }.map { it.value.digest }
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? =
if (this.any { it.tag == tag }) true else null
private fun Array<KeyParameter>.deriveKeySizeFromCurve(): Int {
val curveId = this.find { it.tag == Tag.EC_CURVE }?.value?.ecCurve ?: return 0
return when (curveId) {
EcCurve.P_224 -> 224
EcCurve.P_256 -> 256
EcCurve.P_384 -> 384
EcCurve.P_521 -> 521
EcCurve.CURVE_25519 -> 256
else -> 0
}
}
/** /**
* Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back * Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back
* to KEY_SIZE (field=Integer). * to KEY_SIZE (field=Integer).
@@ -8,7 +8,6 @@ import java.math.BigInteger
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec import java.security.spec.ECGenParameterSpec
import java.security.spec.RSAKeyGenParameterSpec import java.security.spec.RSAKeyGenParameterSpec
import java.util.Date import java.util.Date
@@ -36,6 +35,8 @@ import org.matrix.TEESimulator.logging.SystemLogger
*/ */
object CertificateGenerator { object CertificateGenerator {
private const val UNDEFINED_NOT_AFTER = 253402300799000L
/** /**
* Generates a software-based cryptographic key pair. * Generates a software-based cryptographic key pair.
* *
@@ -49,7 +50,10 @@ object CertificateGenerator {
Algorithm.EC -> "EC" to ECGenParameterSpec(params.ecCurveName) Algorithm.EC -> "EC" to ECGenParameterSpec(params.ecCurveName)
Algorithm.RSA -> Algorithm.RSA ->
"RSA" to "RSA" to
RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent) RSAKeyGenParameterSpec(
params.keySize,
params.rsaPublicExponent ?: RSAKeyGenParameterSpec.F4,
)
else -> else ->
throw IllegalArgumentException( throw IllegalArgumentException(
"Unsupported algorithm: ${params.algorithm}" "Unsupported algorithm: ${params.algorithm}"
@@ -88,11 +92,9 @@ object CertificateGenerator {
"Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})" "Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})"
) )
return runCatching { return try {
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) 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) = val (signingKey, issuer) =
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second } getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
@@ -101,20 +103,20 @@ object CertificateGenerator {
keybox.keyPair to getIssuerFromKeybox(keybox) keybox.keyPair to getIssuerFromKeybox(keybox)
} }
// Build the new leaf certificate with the simulated attestation.
val leafCert = val leafCert =
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel) 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) { if (attestKeyAlias != null) {
listOf(leafCert) listOf(leafCert)
} else { } else {
listOf(leafCert) + keybox.certificates listOf(leafCert) + keybox.certificates
} }
} catch (e: android.os.ServiceSpecificException) {
throw e
} catch (e: Exception) {
SystemLogger.error("Failed to generate certificate chain.", e)
null
} }
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
.getOrNull()
} }
/** /**
@@ -128,7 +130,7 @@ object CertificateGenerator {
params: KeyMintAttestation, params: KeyMintAttestation,
securityLevel: Int, securityLevel: Int,
): Pair<KeyPair, List<Certificate>>? { ): Pair<KeyPair, List<Certificate>>? {
return runCatching { return try {
SystemLogger.info( SystemLogger.info(
"Generating new attested key pair for alias: '$alias' (UID: $uid)" "Generating new attested key pair for alias: '$alias' (UID: $uid)"
) )
@@ -144,11 +146,12 @@ object CertificateGenerator {
"Successfully generated new certificate chain for alias: '$alias'." "Successfully generated new certificate chain for alias: '$alias'."
) )
Pair(newKeyPair, chain) Pair(newKeyPair, chain)
} catch (e: android.os.ServiceSpecificException) {
throw e
} catch (e: Exception) {
SystemLogger.error("Failed to generate attested key pair for alias '$alias'.", e)
null
} }
.onFailure {
SystemLogger.error("Failed to generate attested key pair for alias '$alias'.", it)
}
.getOrNull()
} }
fun getIssuerFromKeybox(keybox: KeyBox) = fun getIssuerFromKeybox(keybox: KeyBox) =
@@ -163,7 +166,10 @@ object CertificateGenerator {
else -> throw IllegalArgumentException("Unsupported algorithm ID: $algorithm") else -> throw IllegalArgumentException("Unsupported algorithm ID: $algorithm")
} }
return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName) return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
?: throw Exception("Could not load keybox for UID $uid and algorithm $algorithmName") ?: throw android.os.ServiceSpecificException(
-75, // ATTESTATION_KEYS_NOT_PROVISIONED
"No attestation key for algorithm $algorithmName in $keyboxFile",
)
} }
/** Retrieves the key pair and issuer name for a given attestation key alias. */ /** Retrieves the key pair and issuer name for a given attestation key alias. */
@@ -214,16 +220,15 @@ object CertificateGenerator {
securityLevel: Int, securityLevel: Int,
): Certificate { ): Certificate {
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key") val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
val leafNotAfter = val notBefore = params.certificateNotBefore ?: Date(0)
(signingKeyPair.public as? X509Certificate)?.notAfter val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER)
?: Date(System.currentTimeMillis() + 31536000000L)
val builder = val builder =
JcaX509v3CertificateBuilder( JcaX509v3CertificateBuilder(
issuer, issuer,
params.certificateSerial ?: BigInteger.ONE, params.certificateSerial ?: BigInteger.ONE,
params.certificateNotBefore ?: Date(), notBefore,
params.certificateNotAfter ?: leafNotAfter, notAfter,
subject, subject,
subjectKeyPair.public, subjectKeyPair.public,
) )