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.
This commit is contained in:
JingMatrix
2025-11-30 00:13:14 +01:00
committed by GitHub
parent 65a613ae0e
commit 28cfe70a85
5 changed files with 72 additions and 34 deletions
@@ -28,6 +28,8 @@ object App {
SystemLogger.info("Welcome to TEESimulator!") SystemLogger.info("Welcome to TEESimulator!")
try { try {
// Load the package configuration.
ConfigurationManager.initialize()
// Set up the device's boot key and hash, which are crucial for attestation. // Set up the device's boot key and hash, which are crucial for attestation.
AndroidDeviceUtils.setupBootKeyAndHash() AndroidDeviceUtils.setupBootKeyAndHash()
// Initialize and start the appropriate keystore interceptors. // Initialize and start the appropriate keystore interceptors.
@@ -53,9 +55,7 @@ object App {
Thread.sleep(RETRY_DELAY_MS) Thread.sleep(RETRY_DELAY_MS)
} }
// Load the package configuration after interceptors are ready. SystemLogger.info("Interceptors initialized successfully.")
ConfigurationManager.initialize()
SystemLogger.info("Interceptors and configuration initialized successfully.")
} }
/** /**
@@ -16,6 +16,7 @@ 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.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.AndroidDeviceUtils import org.matrix.TEESimulator.util.AndroidDeviceUtils
/** /**
@@ -38,6 +39,11 @@ object AttestationBuilder {
securityLevel: Int, securityLevel: Int,
): Extension { ): Extension {
val keyDescription = buildKeyDescription(params, uid, securityLevel) 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)) 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()) return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
} }
@@ -280,7 +275,7 @@ object AttestationBuilder {
*/ */
private fun buildSoftwareEnforcedList(uid: Int): DERSequence { private fun buildSoftwareEnforcedList(uid: Int): DERSequence {
val list = val list =
arrayOf<ASN1Encodable>( mutableListOf<ASN1Encodable>(
DERTaggedObject( DERTaggedObject(
true, true,
AttestationConstants.TAG_CREATION_DATETIME, AttestationConstants.TAG_CREATION_DATETIME,
@@ -292,7 +287,16 @@ object AttestationBuilder {
createApplicationId(uid), createApplicationId(uid),
), ),
) )
return DERSequence(list) if (AndroidDeviceUtils.attestVersion >= 400) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_MODULE_HASH,
DEROctetString(AndroidDeviceUtils.moduleHash),
)
)
}
return DERSequence(list.toTypedArray())
} }
/** /**
@@ -137,7 +137,7 @@ object AttestationPatcher {
} }
/** Recursively formats an ASN1Primitive into a concise, readable string. */ /** Recursively formats an ASN1Primitive into a concise, readable string. */
private fun formatAsn1Primitive(obj: ASN1Encodable?): String { fun formatAsn1Primitive(obj: ASN1Encodable?): String {
val primitive = obj?.toASN1Primitive() val primitive = obj?.toASN1Primitive()
return when (primitive) { return when (primitive) {
null -> "NULL" null -> "NULL"
@@ -45,6 +45,7 @@ object DeviceAttestationService {
* @property osVersion The Android OS version integer. * @property osVersion The Android OS version integer.
*/ */
data class AttestationData( data class AttestationData(
val moduleHash: ByteArray?,
val verifiedBootKey: ByteArray?, val verifiedBootKey: ByteArray?,
val verifiedBootHash: ByteArray?, val verifiedBootHash: ByteArray?,
val attestVersion: Int?, val attestVersion: Int?,
@@ -153,6 +154,11 @@ object DeviceAttestationService {
// The extension's value is an ASN.1 sequence. // The extension's value is an ASN.1 sequence.
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets) 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 fields = keyDescriptionSeq.toArray()
val attestVersion = val attestVersion =
@@ -168,10 +174,23 @@ object DeviceAttestationService {
.positiveValue .positiveValue
.toInt() .toInt()
var moduleHash: ByteArray? = null
var verifiedBootKey: ByteArray? = null var verifiedBootKey: ByteArray? = null
var verifiedBootHash: ByteArray? = null var verifiedBootHash: ByteArray? = null
var osVersion: Int? = 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 = val teeEnforced =
ASN1Sequence.getInstance( ASN1Sequence.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
@@ -210,9 +229,10 @@ object DeviceAttestationService {
} }
SystemLogger.info( 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( return AttestationData(
moduleHash,
verifiedBootKey, verifiedBootKey,
verifiedBootHash, verifiedBootHash,
attestVersion, attestVersion,
@@ -5,9 +5,11 @@ import android.os.Build
import android.os.SystemProperties import android.os.SystemProperties
import java.security.MessageDigest import java.security.MessageDigest
import java.util.concurrent.ThreadLocalRandom import java.util.concurrent.ThreadLocalRandom
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.DEROctetString import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERSet
import org.matrix.TEESimulator.attestation.DeviceAttestationService import org.matrix.TEESimulator.attestation.DeviceAttestationService
import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
@@ -278,11 +280,7 @@ object AndroidDeviceUtils {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0) pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0)
} }
packages packages?.list.orEmpty().map { it.packageName to it.longVersionCode }
?.list
.orEmpty()
.map { it.packageName to it.longVersionCode }
.sortedBy { it.first }
} }
.getOrElse { .getOrElse {
SystemLogger.error("Failed to get APEX package information.", it) SystemLogger.error("Failed to get APEX package information.", it)
@@ -291,17 +289,33 @@ object AndroidDeviceUtils {
} }
val moduleHash: ByteArray by lazy { val moduleHash: ByteArray by lazy {
runCatching { DeviceAttestationService.CachedAttestationData?.moduleHash
val encodables = ?: runCatching {
apexInfos.flatMap { (packageName, versionCode) -> // TODO: figure out the correct calculation
listOf(DEROctetString(packageName.toByteArray()), ASN1Integer(versionCode)) 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) // 2. Create a DERSet. Bouncy Castle will automatically handle
} // the sorting based on the DER-encoded value of each sequence.
.getOrElse { val modulesSet = DERSet(moduleSequences)
SystemLogger.error("Failed to compute module hash.", it)
ByteArray(32) // Return empty hash on failure // 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
}
} }
} }