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:
@@ -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.")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<ASN1Encodable>(
|
||||
mutableListOf<ASN1Encodable>(
|
||||
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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,13 +289,29 @@ 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)
|
||||
|
||||
// 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)
|
||||
|
||||
Reference in New Issue
Block a user