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
|
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.ASN1Boolean
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
import org.bouncycastle.asn1.ASN1Encodable
|
||||||
import org.bouncycastle.asn1.ASN1Enumerated
|
import org.bouncycastle.asn1.ASN1Enumerated
|
||||||
@@ -12,6 +16,7 @@ import org.bouncycastle.asn1.DERSequence
|
|||||||
import org.bouncycastle.asn1.DERSet
|
import org.bouncycastle.asn1.DERSet
|
||||||
import org.bouncycastle.asn1.DERTaggedObject
|
import org.bouncycastle.asn1.DERTaggedObject
|
||||||
import org.bouncycastle.asn1.x509.Extension
|
import org.bouncycastle.asn1.x509.Extension
|
||||||
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,11 +29,16 @@ object AttestationBuilder {
|
|||||||
* Builds the complete X.509 attestation extension.
|
* Builds the complete X.509 attestation extension.
|
||||||
*
|
*
|
||||||
* @param params The parsed key generation parameters.
|
* @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.
|
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
|
||||||
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
||||||
*/
|
*/
|
||||||
fun buildAttestationExtension(params: KeyMintAttestation, securityLevel: Int): Extension {
|
fun buildAttestationExtension(
|
||||||
val keyDescription = buildKeyDescription(params, securityLevel)
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): Extension {
|
||||||
|
val keyDescription = buildKeyDescription(params, uid, securityLevel)
|
||||||
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
|
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. */
|
/** 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 teeEnforced = buildTeeEnforcedList(params)
|
||||||
val softwareEnforced = buildSoftwareEnforcedList()
|
val softwareEnforced = buildSoftwareEnforcedList(uid)
|
||||||
|
|
||||||
val fields =
|
val fields =
|
||||||
arrayOf(
|
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 {
|
params.manufacturer?.let {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -220,32 +261,16 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
params.imei?.let {
|
if (AndroidDeviceUtils.attestVersion >= 300) {
|
||||||
list.add(
|
params.secondImei?.let {
|
||||||
DERTaggedObject(
|
list.add(
|
||||||
true,
|
DERTaggedObject(
|
||||||
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
true,
|
||||||
DEROctetString(it),
|
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) {
|
if (AndroidDeviceUtils.attestVersion >= 400) {
|
||||||
@@ -265,18 +290,101 @@ 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(): DERSequence {
|
private fun buildSoftwareEnforcedList(uid: Int): DERSequence {
|
||||||
val list =
|
val list =
|
||||||
arrayOf<ASN1Encodable>(
|
arrayOf<ASN1Encodable>(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_CREATION_DATETIME,
|
AttestationConstants.TAG_CREATION_DATETIME,
|
||||||
ASN1Integer(System.currentTimeMillis()),
|
ASN1Integer(System.currentTimeMillis()),
|
||||||
)
|
),
|
||||||
// The ATTESTATION_APPLICATION_ID is technically software-enforced, but we are
|
DERTaggedObject(
|
||||||
// omitting it
|
true,
|
||||||
// for this simulation as it is complex to generate correctly for arbitrary UIDs.
|
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
|
||||||
|
createApplicationId(uid),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return DERSequence(list)
|
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 brand: ByteArray?,
|
||||||
val device: ByteArray?,
|
val device: ByteArray?,
|
||||||
val product: ByteArray?,
|
val product: ByteArray?,
|
||||||
|
val serial: ByteArray?,
|
||||||
|
val imei: ByteArray?,
|
||||||
|
val meid: ByteArray?,
|
||||||
val manufacturer: ByteArray?,
|
val manufacturer: ByteArray?,
|
||||||
val model: ByteArray?,
|
val model: ByteArray?,
|
||||||
val imei: ByteArray?,
|
|
||||||
val secondImei: ByteArray?,
|
val secondImei: ByteArray?,
|
||||||
val meid: ByteArray?,
|
|
||||||
) {
|
) {
|
||||||
/** 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(
|
||||||
@@ -82,11 +83,12 @@ data class KeyMintAttestation(
|
|||||||
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
||||||
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
||||||
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
|
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),
|
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
|
||||||
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
||||||
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
|
||||||
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
||||||
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
|
||||||
) {
|
) {
|
||||||
// Log all parsed parameters for debugging purposes.
|
// Log all parsed parameters for debugging purposes.
|
||||||
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ object CertificateGenerator {
|
|||||||
|
|
||||||
// Build the new leaf certificate with the simulated attestation.
|
// Build the new leaf certificate with the simulated attestation.
|
||||||
val leafCert =
|
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
|
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||||
// chain.
|
// chain.
|
||||||
@@ -177,6 +177,7 @@ object CertificateGenerator {
|
|||||||
signingKeyPair: KeyPair,
|
signingKeyPair: KeyPair,
|
||||||
issuer: X500Name,
|
issuer: X500Name,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
||||||
@@ -197,7 +198,9 @@ object CertificateGenerator {
|
|||||||
// Add standard extensions.
|
// Add standard extensions.
|
||||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
||||||
// Add our custom, simulated attestation extension.
|
// Add our custom, simulated attestation extension.
|
||||||
builder.addExtension(AttestationBuilder.buildAttestationExtension(params, securityLevel))
|
builder.addExtension(
|
||||||
|
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||||
|
)
|
||||||
|
|
||||||
val signerAlgorithm =
|
val signerAlgorithm =
|
||||||
when (params.algorithm) {
|
when (params.algorithm) {
|
||||||
|
|||||||
Reference in New Issue
Block a user