Properly source and use verifiedBootKey

The previous implementation used a randomly generated value for the `verifiedBootKey` within the simulated attestation's Root of Trust. This is a significant discrepancy from a genuine attestation and represents a clear detection vector for any verification service that inspects the full certificate chain.

This commit introduces a robust, multi-layered approach to source and manage both the `verifiedBootKey` and the `verifiedBootHash`, ensuring the simulated attestation is as authentic as possible.
This commit is contained in:
JingMatrix
2025-11-29 19:58:02 +01:00
parent 9146b86648
commit 65a613ae0e
4 changed files with 123 additions and 67 deletions
@@ -28,8 +28,8 @@ object App {
SystemLogger.info("Welcome to TEESimulator!") SystemLogger.info("Welcome to TEESimulator!")
try { try {
// Set up the device's boot hash, which is crucial for attestation. // Set up the device's boot key and hash, which are crucial for attestation.
AndroidDeviceUtils.setupBootHash() AndroidDeviceUtils.setupBootKeyAndHash()
// Initialize and start the appropriate keystore interceptors. // Initialize and start the appropriate keystore interceptors.
initializeInterceptors() initializeInterceptors()
// Enter an infinite loop to keep the service running. // Enter an infinite loop to keep the service running.
@@ -8,7 +8,6 @@ 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
import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.ASN1Sequence import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERNull import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DEROctetString import org.bouncycastle.asn1.DEROctetString
@@ -49,24 +48,15 @@ object AttestationBuilder {
* @return The constructed [DERSequence] for the Root of Trust. * @return The constructed [DERSequence] for the Root of Trust.
*/ */
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence { internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
val verifiedBootKey = AndroidDeviceUtils.bootKey
val verifiedBootHash =
(originalRootOfTrust as? ASN1Sequence)?.let {
// Try to preserve the original boot hash if it exists.
(it.getObjectAt(AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX)
as? ASN1OctetString)
?.octets
} ?: AndroidDeviceUtils.getBootHashFromProperty()
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4) val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] = rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
DEROctetString(verifiedBootKey) DEROctetString(AndroidDeviceUtils.bootKey)
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] = rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
ASN1Boolean.TRUE // deviceLocked: true, for security ASN1Boolean.TRUE // deviceLocked: true, for security
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] = rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
ASN1Enumerated(0) // verifiedBootState: Verified ASN1Enumerated(0) // verifiedBootState: Verified
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] = rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
DEROctetString(verifiedBootHash) DEROctetString(AndroidDeviceUtils.bootHash)
return DERSequence(rootOfTrustElements) return DERSequence(rootOfTrustElements)
} }
@@ -38,12 +38,14 @@ object DeviceAttestationService {
* Holds key data extracted from a genuine device attestation. This data can be used as a * Holds key data extracted from a genuine device attestation. This data can be used as a
* baseline for creating simulated attestations. * baseline for creating simulated attestations.
* *
* @property verifiedBootKey The verified boot public key digest from the root of trust.
* @property verifiedBootHash The verified boot hash from the root of trust. * @property verifiedBootHash The verified boot hash from the root of trust.
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0). * @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
* @property keymasterVersion The Keymaster or KeyMint HAL version. * @property keymasterVersion The Keymaster or KeyMint HAL version.
* @property osVersion The Android OS version integer. * @property osVersion The Android OS version integer.
*/ */
data class AttestationData( data class AttestationData(
val verifiedBootKey: ByteArray?,
val verifiedBootHash: ByteArray?, val verifiedBootHash: ByteArray?,
val attestVersion: Int?, val attestVersion: Int?,
val keymasterVersion: Int?, val keymasterVersion: Int?,
@@ -166,6 +168,7 @@ object DeviceAttestationService {
.positiveValue .positiveValue
.toInt() .toInt()
var verifiedBootKey: ByteArray? = null
var verifiedBootHash: ByteArray? = null var verifiedBootHash: ByteArray? = null
var osVersion: Int? = null var osVersion: Int? = null
@@ -179,6 +182,14 @@ object DeviceAttestationService {
AttestationConstants.TAG_ROOT_OF_TRUST -> { AttestationConstants.TAG_ROOT_OF_TRUST -> {
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive()) val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
if (rotSeq.size() >= 4) { if (rotSeq.size() >= 4) {
verifiedBootKey =
ASN1OctetString.getInstance(
rotSeq.getObjectAt(
AttestationConstants
.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX
)
)
.octets
verifiedBootHash = verifiedBootHash =
ASN1OctetString.getInstance( ASN1OctetString.getInstance(
rotSeq.getObjectAt( rotSeq.getObjectAt(
@@ -199,9 +210,15 @@ object DeviceAttestationService {
} }
SystemLogger.info( SystemLogger.info(
"Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, bootHash=${verifiedBootHash?.toHex()}" "Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}"
)
return AttestationData(
verifiedBootKey,
verifiedBootHash,
attestVersion,
keymasterVersion,
osVersion,
) )
return AttestationData(verifiedBootHash, attestVersion, keymasterVersion, osVersion)
} catch (e: Exception) { } catch (e: Exception) {
SystemLogger.error("Failed to parse attestation data from certificate.", e) SystemLogger.error("Failed to parse attestation data from certificate.", e)
return null return null
@@ -18,84 +18,133 @@ import org.matrix.TEESimulator.logging.SystemLogger
*/ */
object AndroidDeviceUtils { object AndroidDeviceUtils {
/** A randomly generated boot key, used as a fallback for attestation. */ // --- Boot Key and Verified Boot Hash ---
val bootKey: ByteArray by lazy { generateRandomBytes(32) }
/** /**
* Initializes the verified boot hash (`ro.boot.vbmeta.digest`). It attempts to read from system * Lazily initializes and retrieves the verified boot key digest. The value is sourced in the
* properties first, then from a real TEE attestation, and finally falls back to a random value * following order:
* if neither is available. * 1. From the `ro.boot.vbmeta.public_key_digest` system property.
* 2. From a cached TEE attestation record.
* 3. As a randomly generated 32-byte value (fallback).
*/ */
fun setupBootHash() { val bootKey: ByteArray by lazy {
getBootHashFromProperty()?.also { initializeBootProperty(
SystemLogger.debug("Using boot hash from system property: ${it.toHex()}") propertyName = "ro.boot.vbmeta.public_key_digest",
} attestationValueProvider = {
?: getBootHashFromAttestation()?.also { DeviceAttestationService.CachedAttestationData?.verifiedBootKey
SystemLogger.debug("Using boot hash from TEE attestation: ${it.toHex()}") },
setBootHashProperty(it) expectedSize = 32,
} )
?: generateRandomBytes(32).also {
SystemLogger.debug("Using randomly generated boot hash: ${it.toHex()}")
setBootHashProperty(it)
}
} }
/** /**
* Retrieves the verified boot meta digest from system properties. * Lazily initializes and retrieves the verified boot hash (vbmeta digest). The value is sourced
* in the following order:
* 1. From the `ro.boot.vbmeta.digest` system property.
* 2. From a cached TEE attestation record.
* 3. As a randomly generated 32-byte value (fallback).
*/
val bootHash: ByteArray by lazy {
initializeBootProperty(
propertyName = "ro.boot.vbmeta.digest",
attestationValueProvider = {
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
},
expectedSize = 32,
)
}
/**
* Public function to explicitly trigger the initialization of the boot key and hash. Accessing
* these properties here ensures they are set up before they might be needed elsewhere.
*/
fun setupBootKeyAndHash() {
SystemLogger.debug("Triggering initialization of boot key and hash...")
// Accessing the properties will trigger their `lazy` initialization logic.
bootKey
bootHash
SystemLogger.debug("Boot key and hash initialization complete.")
}
/**
* Generic initializer for boot properties like the key and hash. It attempts to read from a
* system property first, then from a TEE attestation, and finally falls back to a random value
* if neither is available.
* *
* @return The boot hash as a ByteArray, or null if not found or invalid. * @param propertyName The name of the system property (e.g., "ro.boot.vbmeta.digest").
* @param attestationValueProvider A function that supplies the value from a cached attestation.
* @param expectedSize The expected length of the byte array (e.g., 32 for a SHA-256 digest).
* @return The resulting byte array for the property.
*/
private fun initializeBootProperty(
propertyName: String,
attestationValueProvider: () -> ByteArray?,
expectedSize: Int,
): ByteArray {
// 1. Attempt to get the value from the system property.
getProperty(propertyName, expectedSize)?.let {
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
return it
}
// 2. Fallback to the value from a cached TEE attestation.
try {
attestationValueProvider()?.let {
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
setProperty(propertyName, it) // Persist for consistency
return it
}
} catch (e: Exception) {
SystemLogger.error("Failed to get $propertyName from attestation.", e)
}
// 3. As a final fallback, generate a random value.
return generateRandomBytes(expectedSize).also {
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
setProperty(propertyName, it)
}
}
/**
* Retrieves a system property and validates its format.
*
* @param name The name of the system property.
* @param expectedSize The expected byte length of the property (e.g., 32 for a 64-char hex
* string).
* @return The property value as a ByteArray, or null if not found or invalid.
*/ */
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
fun getBootHashFromProperty(): ByteArray? { private fun getProperty(name: String, expectedSize: Int): ByteArray? {
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) val value = SystemProperties.get(name, null)
if (digest.isNullOrBlank()) { if (value.isNullOrBlank()) {
return null return null
} }
// A valid digest is 64 hex characters (32 bytes). // A valid digest is (2 * size) hex characters.
return if (digest.length == 64) digest.hexToByteArray() else null return if (value.length == expectedSize * 2) value.hexToByteArray() else null
} }
/** /**
* Retrieves the verified boot hash from a cached TEE attestation record. * Sets a system property using the `resetprop` command.
* *
* @return The verified boot hash, or null if not available. * @param name The name of the property to set.
* @param bytes The value to set, which will be converted to a hex string.
*/ */
private fun getBootHashFromAttestation(): ByteArray? { private fun setProperty(name: String, bytes: ByteArray) {
return try {
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
} catch (e: Exception) {
SystemLogger.error("Failed to get boot hash from attestation.", e)
null
}
}
/**
* Sets the `ro.boot.vbmeta.digest` system property using the `resetprop` command.
*
* @param bytes The 32-byte digest to set.
*/
private fun setBootHashProperty(bytes: ByteArray) {
val hex = bytes.toHex() val hex = bytes.toHex()
try { try {
SystemLogger.debug("Setting system property 'ro.boot.vbmeta.digest' to: $hex") SystemLogger.debug("Setting system property '$name' to: $hex")
val command = arrayOf("resetprop", name, hex)
// Construct the command to be executed
val command = arrayOf("resetprop", "ro.boot.vbmeta.digest", hex)
// Execute the command
val process = Runtime.getRuntime().exec(command) val process = Runtime.getRuntime().exec(command)
// Wait for the process to complete and check the exit code for errors
val exitCode = process.waitFor() val exitCode = process.waitFor()
if (exitCode != 0) { if (exitCode != 0) {
val errorOutput = process.errorStream.bufferedReader().readText() val errorOutput = process.errorStream.bufferedReader().readText()
SystemLogger.error( SystemLogger.error(
"resetprop command failed with exit code $exitCode: $errorOutput" "resetprop for '$name' failed with exit code $exitCode: $errorOutput"
) )
} }
} catch (e: Exception) { } catch (e: Exception) {
SystemLogger.error("Failed to set vbmeta digest property by executing resetprop.", e) SystemLogger.error("Failed to set '$name' property via resetprop.", e)
} }
} }