Fix software enforced list for certificates generation (#28)
Properly implement the `ATTESTATION_APPLICATION_ID` tag into key description. Moreover, we add the `ATTESTATION_ID_SERIAL` tag to the TEE enforced list, and re-order all tags to remain consistent with the object `AttestationConstants`.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
package org.matrix.TEESimulator.attestation
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import org.bouncycastle.asn1.ASN1Boolean
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.ASN1Enumerated
|
||||
@@ -12,6 +16,7 @@ import org.bouncycastle.asn1.DERSequence
|
||||
import org.bouncycastle.asn1.DERSet
|
||||
import org.bouncycastle.asn1.DERTaggedObject
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||
|
||||
/**
|
||||
@@ -24,11 +29,16 @@ object AttestationBuilder {
|
||||
* Builds the complete X.509 attestation extension.
|
||||
*
|
||||
* @param params The parsed key generation parameters.
|
||||
* @param uid The UID of the application requesting attestation.
|
||||
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
|
||||
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
||||
*/
|
||||
fun buildAttestationExtension(params: KeyMintAttestation, securityLevel: Int): Extension {
|
||||
val keyDescription = buildKeyDescription(params, securityLevel)
|
||||
fun buildAttestationExtension(
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): Extension {
|
||||
val keyDescription = buildKeyDescription(params, uid, securityLevel)
|
||||
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
|
||||
}
|
||||
|
||||
@@ -94,9 +104,13 @@ object AttestationBuilder {
|
||||
}
|
||||
|
||||
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
||||
private fun buildKeyDescription(params: KeyMintAttestation, securityLevel: Int): ASN1Sequence {
|
||||
private fun buildKeyDescription(
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): ASN1Sequence {
|
||||
val teeEnforced = buildTeeEnforcedList(params)
|
||||
val softwareEnforced = buildSoftwareEnforcedList()
|
||||
val softwareEnforced = buildSoftwareEnforcedList(uid)
|
||||
|
||||
val fields =
|
||||
arrayOf(
|
||||
@@ -202,6 +216,33 @@ object AttestationBuilder {
|
||||
)
|
||||
)
|
||||
}
|
||||
params.serial?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_SERIAL,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.imei?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.meid?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_MEID,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.manufacturer?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
@@ -220,32 +261,16 @@ object AttestationBuilder {
|
||||
)
|
||||
)
|
||||
}
|
||||
params.imei?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
||||
DEROctetString(it),
|
||||
if (AndroidDeviceUtils.attestVersion >= 300) {
|
||||
params.secondImei?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
params.secondImei?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.meid?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_MEID,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (AndroidDeviceUtils.attestVersion >= 400) {
|
||||
@@ -265,18 +290,101 @@ object AttestationBuilder {
|
||||
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
||||
* Keystore.
|
||||
*/
|
||||
private fun buildSoftwareEnforcedList(): DERSequence {
|
||||
private fun buildSoftwareEnforcedList(uid: Int): DERSequence {
|
||||
val list =
|
||||
arrayOf<ASN1Encodable>(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_CREATION_DATETIME,
|
||||
ASN1Integer(System.currentTimeMillis()),
|
||||
)
|
||||
// The ATTESTATION_APPLICATION_ID is technically software-enforced, but we are
|
||||
// omitting it
|
||||
// for this simulation as it is complex to generate correctly for arbitrary UIDs.
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
|
||||
createApplicationId(uid),
|
||||
),
|
||||
)
|
||||
return DERSequence(list)
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a byte array that provides content-based equality. This is necessary for using
|
||||
* signature digests in a Set.
|
||||
*/
|
||||
private data class Digest(val digest: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return digest.contentEquals((other as Digest).digest)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = digest.contentHashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the AttestationApplicationId structure. This structure contains information about the
|
||||
* package(s) and their signing certificates.
|
||||
*
|
||||
* @param uid The UID of the application.
|
||||
* @return A DER-encoded octet string containing the application ID information.
|
||||
* @throws IllegalStateException If the PackageManager or package information cannot be
|
||||
* retrieved.
|
||||
*/
|
||||
@Throws(Throwable::class)
|
||||
private fun createApplicationId(uid: Int): DEROctetString {
|
||||
val pm =
|
||||
ConfigurationManager.getPackageManager()
|
||||
?: throw IllegalStateException("PackageManager not found!")
|
||||
val packages =
|
||||
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||
|
||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||
val packageInfoList = mutableListOf<DERSequence>()
|
||||
val signatureDigests = mutableSetOf<Digest>()
|
||||
|
||||
// Process all packages associated with the UID in a single loop.
|
||||
packages.forEach { packageName ->
|
||||
val userId = uid / 100000
|
||||
val packageInfo =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
pm.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
|
||||
userId,
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
|
||||
}
|
||||
|
||||
// Add package information (name and version code) to our list.
|
||||
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 ->
|
||||
val digest = sha256.digest(signature.toByteArray())
|
||||
signatureDigests.add(Digest(digest))
|
||||
}
|
||||
}
|
||||
|
||||
// The application ID is a sequence of two sets:
|
||||
// 1. A set of package information (name and version).
|
||||
// 2. A set of SHA-256 digests of the signing certificates.
|
||||
val applicationIdSequence =
|
||||
DERSequence(
|
||||
arrayOf(
|
||||
DERSet(packageInfoList.toTypedArray()),
|
||||
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()),
|
||||
)
|
||||
)
|
||||
|
||||
return DEROctetString(applicationIdSequence.encoded)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,12 @@ data class KeyMintAttestation(
|
||||
val brand: ByteArray?,
|
||||
val device: ByteArray?,
|
||||
val product: ByteArray?,
|
||||
val serial: ByteArray?,
|
||||
val imei: ByteArray?,
|
||||
val meid: ByteArray?,
|
||||
val manufacturer: ByteArray?,
|
||||
val model: ByteArray?,
|
||||
val imei: ByteArray?,
|
||||
val secondImei: ByteArray?,
|
||||
val meid: ByteArray?,
|
||||
) {
|
||||
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
||||
constructor(
|
||||
@@ -82,11 +83,12 @@ data class KeyMintAttestation(
|
||||
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
||||
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
||||
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
|
||||
serial = params.findBlob(Tag.ATTESTATION_ID_SERIAL),
|
||||
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
||||
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
||||
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
|
||||
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
||||
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
||||
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
||||
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
||||
) {
|
||||
// Log all parsed parameters for debugging purposes.
|
||||
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
||||
|
||||
@@ -112,7 +112,7 @@ object CertificateGenerator {
|
||||
|
||||
// Build the new leaf certificate with the simulated attestation.
|
||||
val leafCert =
|
||||
buildCertificate(newKeyPair, signingKey, issuer, params, securityLevel)
|
||||
buildCertificate(newKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||
|
||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||
// chain.
|
||||
@@ -177,6 +177,7 @@ object CertificateGenerator {
|
||||
signingKeyPair: KeyPair,
|
||||
issuer: X500Name,
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): Certificate {
|
||||
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
||||
@@ -197,7 +198,9 @@ object CertificateGenerator {
|
||||
// Add standard extensions.
|
||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
||||
// Add our custom, simulated attestation extension.
|
||||
builder.addExtension(AttestationBuilder.buildAttestationExtension(params, securityLevel))
|
||||
builder.addExtension(
|
||||
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||
)
|
||||
|
||||
val signerAlgorithm =
|
||||
when (params.algorithm) {
|
||||
|
||||
Reference in New Issue
Block a user