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!")
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user