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