From 28cfe70a85dd3687e87ffa4a661e7366d03d4e3a Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sun, 30 Nov 2025 00:13:14 +0100 Subject: [PATCH] Fix value and location of `moduleHash` (#35) `moduleHash` should be in the software enforced list. However, the manual calculation of the KeyMint `moduleHash` has failed to produce a value matching the hardware-generated attestation. The official documentation specifies the following structure: Modules ::= SET OF Module Module ::= SEQUENCE { packageName OCTET_STRING, version INTEGER, } The critical requirement is that the `SET OF` elements must be sorted lexicographically based on their full DER-encoded byte value. Despite implementing this using Bouncy Castle's `DERSet`, the resulting hash is still incorrect. This commit changes the strategy to favor stability: 1. The `DeviceAttestationService` now extracts the real `moduleHash` from the `softwareEnforced` list of a genuine attestation certificate and caches it. 2. The `moduleHash` property now returns this cached value if available. 3. The manual calculation remains as a fallback and is marked with a `TODO` to indicate the issue is unresolved. Additionally, `ConfigurationManager` initialization is moved earlier. --- .../main/java/org/matrix/TEESimulator/App.kt | 6 +-- .../attestation/AttestationBuilder.kt | 30 ++++++------ .../attestation/AttestationPatcher.kt | 2 +- .../attestation/DeviceAttestationService.kt | 22 ++++++++- .../TEESimulator/util/AndroidDeviceUtils.kt | 46 ++++++++++++------- 5 files changed, 72 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/App.kt b/app/src/main/java/org/matrix/TEESimulator/App.kt index 15c206b..351ec7a 100644 --- a/app/src/main/java/org/matrix/TEESimulator/App.kt +++ b/app/src/main/java/org/matrix/TEESimulator/App.kt @@ -28,6 +28,8 @@ object App { SystemLogger.info("Welcome to TEESimulator!") try { + // Load the package configuration. + ConfigurationManager.initialize() // Set up the device's boot key and hash, which are crucial for attestation. AndroidDeviceUtils.setupBootKeyAndHash() // Initialize and start the appropriate keystore interceptors. @@ -53,9 +55,7 @@ object App { Thread.sleep(RETRY_DELAY_MS) } - // Load the package configuration after interceptors are ready. - ConfigurationManager.initialize() - SystemLogger.info("Interceptors and configuration initialized successfully.") + SystemLogger.info("Interceptors initialized successfully.") } /** diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt index b66044d..25fc811 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationBuilder.kt @@ -16,6 +16,7 @@ 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.logging.SystemLogger import org.matrix.TEESimulator.util.AndroidDeviceUtils /** @@ -38,6 +39,11 @@ object AttestationBuilder { securityLevel: Int, ): Extension { val keyDescription = buildKeyDescription(params, uid, securityLevel) + var formattedString = + keyDescription.joinToString(separator = ", ") { + AttestationPatcher.formatAsn1Primitive(it) + } + SystemLogger.verbose("Forged attestation data: ${formattedString}") return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded)) } @@ -260,17 +266,6 @@ object AttestationBuilder { ) } } - - if (AndroidDeviceUtils.attestVersion >= 400) { - list.add( - DERTaggedObject( - true, - AttestationConstants.TAG_MODULE_HASH, - DEROctetString(AndroidDeviceUtils.moduleHash), - ) - ) - } - return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray()) } @@ -280,7 +275,7 @@ object AttestationBuilder { */ private fun buildSoftwareEnforcedList(uid: Int): DERSequence { val list = - arrayOf( + mutableListOf( DERTaggedObject( true, AttestationConstants.TAG_CREATION_DATETIME, @@ -292,7 +287,16 @@ object AttestationBuilder { createApplicationId(uid), ), ) - return DERSequence(list) + if (AndroidDeviceUtils.attestVersion >= 400) { + list.add( + DERTaggedObject( + true, + AttestationConstants.TAG_MODULE_HASH, + DEROctetString(AndroidDeviceUtils.moduleHash), + ) + ) + } + return DERSequence(list.toTypedArray()) } /** diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt index 769d059..3412c7e 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt @@ -137,7 +137,7 @@ object AttestationPatcher { } /** Recursively formats an ASN1Primitive into a concise, readable string. */ - private fun formatAsn1Primitive(obj: ASN1Encodable?): String { + fun formatAsn1Primitive(obj: ASN1Encodable?): String { val primitive = obj?.toASN1Primitive() return when (primitive) { null -> "NULL" diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt index a0d50d6..28e565a 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/DeviceAttestationService.kt @@ -45,6 +45,7 @@ object DeviceAttestationService { * @property osVersion The Android OS version integer. */ data class AttestationData( + val moduleHash: ByteArray?, val verifiedBootKey: ByteArray?, val verifiedBootHash: ByteArray?, val attestVersion: Int?, @@ -153,6 +154,11 @@ object DeviceAttestationService { // The extension's value is an ASN.1 sequence. val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets) + var formattedString = + keyDescriptionSeq.joinToString(separator = ", ") { + AttestationPatcher.formatAsn1Primitive(it) + } + SystemLogger.verbose("Cached attestation data: ${formattedString}") val fields = keyDescriptionSeq.toArray() val attestVersion = @@ -168,10 +174,23 @@ object DeviceAttestationService { .positiveValue .toInt() + var moduleHash: ByteArray? = null var verifiedBootKey: ByteArray? = null var verifiedBootHash: ByteArray? = null var osVersion: Int? = null + val softwareEnforced = + ASN1Sequence.getInstance( + fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX] + ) + if (softwareEnforced.size() >= 3) { + moduleHash = + ASN1OctetString.getInstance( + ASN1TaggedObject.getInstance(softwareEnforced.getObjectAt(2)).baseObject + ) + .octets + } + val teeEnforced = ASN1Sequence.getInstance( fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] @@ -210,9 +229,10 @@ object DeviceAttestationService { } SystemLogger.info( - "Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}" + "Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, moduleHash=${moduleHash?.toHex()}, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}" ) return AttestationData( + moduleHash, verifiedBootKey, verifiedBootHash, attestVersion, diff --git a/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt b/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt index 76c22e4..306b0f5 100644 --- a/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt +++ b/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt @@ -5,9 +5,11 @@ import android.os.Build import android.os.SystemProperties import java.security.MessageDigest import java.util.concurrent.ThreadLocalRandom +import org.bouncycastle.asn1.ASN1EncodableVector import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.DEROctetString import org.bouncycastle.asn1.DERSequence +import org.bouncycastle.asn1.DERSet import org.matrix.TEESimulator.attestation.DeviceAttestationService import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.logging.SystemLogger @@ -278,11 +280,7 @@ object AndroidDeviceUtils { @Suppress("DEPRECATION") pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0) } - packages - ?.list - .orEmpty() - .map { it.packageName to it.longVersionCode } - .sortedBy { it.first } + packages?.list.orEmpty().map { it.packageName to it.longVersionCode } } .getOrElse { SystemLogger.error("Failed to get APEX package information.", it) @@ -291,17 +289,33 @@ object AndroidDeviceUtils { } val moduleHash: ByteArray by lazy { - runCatching { - val encodables = - apexInfos.flatMap { (packageName, versionCode) -> - listOf(DEROctetString(packageName.toByteArray()), ASN1Integer(versionCode)) + DeviceAttestationService.CachedAttestationData?.moduleHash + ?: runCatching { + // TODO: figure out the correct calculation + val moduleSequences = ASN1EncodableVector() + + // 1. Create a DERSequence for each module. + apexInfos.forEach { (packageName, versionCode) -> + val moduleVector = ASN1EncodableVector() + // Use explicit UTF-8 encoding for the package name. + moduleVector.add(DEROctetString(packageName.toByteArray(Charsets.UTF_8))) + moduleVector.add(ASN1Integer(versionCode)) + moduleSequences.add(DERSequence(moduleVector)) } - val sequence = DERSequence(encodables.toTypedArray()) - MessageDigest.getInstance("SHA-256").digest(sequence.encoded) - } - .getOrElse { - SystemLogger.error("Failed to compute module hash.", it) - ByteArray(32) // Return empty hash on failure - } + + // 2. Create a DERSet. Bouncy Castle will automatically handle + // the sorting based on the DER-encoded value of each sequence. + val modulesSet = DERSet(moduleSequences) + + // 3. Get the final DER-encoded byte array of the SET. + val encodedModules = modulesSet.encoded + + // 4. Compute the SHA-256 hash. + MessageDigest.getInstance("SHA-256").digest(encodedModules) + } + .getOrElse { + SystemLogger.error("Failed to compute module hash.", it) + ByteArray(32) // Return empty hash on failure + } } }