Implement per-package security patch configuration (#49)
This commit introduces a hierarchical configuration system for the security patch levels reported in attestations, allowing for both global defaults and per-package overrides. The `security_patch.txt` file is enhanced to support this new syntax. Settings at the top of the file act as a global default, which can be overridden for specific applications by defining settings under a `[package.name]` section.
This commit is contained in:
@@ -76,12 +76,61 @@ org.matrix.demo
|
|||||||
|
|
||||||
### Security Patch Level (`security_patch.txt`)
|
### Security Patch Level (`security_patch.txt`)
|
||||||
|
|
||||||
This allows you to configure the security patch level that the simulator will report in its forged attestation certificates.
|
This file allows you to configure the `osPatchLevel`, `vendorPatchLevel`, and `bootPatchLevel` that the simulator will report in its patched or forged attestation certificates.
|
||||||
|
|
||||||
|
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change the actual system properties of your device.
|
||||||
|
|
||||||
|
#### Global and Per-Package Configuration
|
||||||
|
|
||||||
|
You can set a global patch level that applies to all applications, and you can also override these settings for specific packages. The syntax is hierarchical:
|
||||||
|
|
||||||
|
* Settings defined at the top of the file, before any `[package.name]` line, are **global** and serve as the default for all apps.
|
||||||
|
* To create a specific configuration for an application, add its package name in square brackets (e.g., `[com.google.android.gms]`). All settings following this line will apply *only* to that package until a new package context is declared.
|
||||||
|
|
||||||
|
#### Configuration Keys and Values
|
||||||
|
|
||||||
|
You can specify the patch level for the following components using a `key=value` format:
|
||||||
|
|
||||||
|
* `system`: The main OS patch level.
|
||||||
|
* `vendor`: The vendor patch level.
|
||||||
|
* `boot`: The boot/kernel patch level.
|
||||||
|
* `all`: A convenient shorthand to set the same date for `system`, `vendor`, and `boot` simultaneously. Any individual key can still be used to override the value set by `all`.
|
||||||
|
|
||||||
|
Dates should be provided in `YYYY-MM-DD` format (e.g., `2025-11-05`).
|
||||||
|
|
||||||
|
#### Special Keywords
|
||||||
|
|
||||||
|
In addition to date values, two special keywords provide advanced control:
|
||||||
|
|
||||||
|
* **`no`**: This keyword instructs the simulator to **completely omit** the corresponding patch level tag from the generated attestation.
|
||||||
|
|
||||||
|
* **`device_default`**: This keyword forces the simulator to fall back and use the device's **real hardware value** for that specific patch level. This is essential for creating exceptions to a global override or an `all` rule.
|
||||||
|
|
||||||
|
#### Example Configuration
|
||||||
|
|
||||||
|
This example demonstrates how to combine global settings, per-package overrides, and special keywords for fine-grained control.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Advanced Configuration
|
# --- Global Configuration ---
|
||||||
system=2025-11
|
# This is the default for all apps unless specified otherwise.
|
||||||
boot=no # Do not report a boot patch level
|
# - Forge a recent system patch level.
|
||||||
vendor=20251101 # Report a specific vendor patch level
|
# - Use the device's real vendor patch level.
|
||||||
|
# - Do not report a boot patch level at all.
|
||||||
|
system=2025-11-05
|
||||||
|
vendor=device_default
|
||||||
|
boot=no
|
||||||
|
|
||||||
|
# --- Per-Package Override for Google Play Services ---
|
||||||
|
# This app will report an older, specific date for its system patch.
|
||||||
|
# It will inherit the global settings for vendor (device_default) and boot (no).
|
||||||
|
[com.google.android.gms]
|
||||||
|
system=2024-10-01
|
||||||
|
|
||||||
|
# --- Per-Package Override for a Demo App ---
|
||||||
|
# This app gets a completely custom configuration.
|
||||||
|
[org.matrix.demo]
|
||||||
|
# Set a base date for all patch levels...
|
||||||
|
all=2025-09-15
|
||||||
|
# ...but make an exception: use the real boot patch level instead of the one from 'all'.
|
||||||
|
boot=device_default
|
||||||
```
|
```
|
||||||
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ 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.logging.SystemLogger
|
||||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||||
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils.DO_NOT_REPORT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
||||||
@@ -67,34 +68,62 @@ object AttestationBuilder {
|
|||||||
return DERSequence(rootOfTrustElements)
|
return DERSequence(rootOfTrustElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Assembles a map of simulated hardware-enforced properties. */
|
/**
|
||||||
fun getSimulatedHardwareProperties(): Map<Int, DERTaggedObject> {
|
* Assembles a map representing the desired state of simulated hardware-enforced properties. A
|
||||||
return mapOf(
|
* null value for a given tag indicates that it should be removed from the attestation.
|
||||||
AttestationConstants.TAG_OS_VERSION to
|
*
|
||||||
DERTaggedObject(
|
* @param uid The UID of the calling application.
|
||||||
true,
|
* @return A map where keys are attestation tag numbers and values are the desired
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
* [DERTaggedObject] or null to signify removal.
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
*/
|
||||||
),
|
fun getSimulatedHardwareProperties(uid: Int): Map<Int, DERTaggedObject?> {
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL to
|
val properties = mutableMapOf<Int, DERTaggedObject?>()
|
||||||
|
|
||||||
|
// OS Version is always present.
|
||||||
|
properties[AttestationConstants.TAG_OS_VERSION] =
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_OS_VERSION,
|
||||||
|
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
||||||
|
)
|
||||||
|
|
||||||
|
val osPatch = AndroidDeviceUtils.getPatchLevel(uid)
|
||||||
|
properties[AttestationConstants.TAG_OS_PATCHLEVEL] =
|
||||||
|
if (osPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
AttestationConstants.TAG_OS_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
ASN1Integer(osPatch.toLong()),
|
||||||
),
|
)
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL to
|
} else {
|
||||||
|
null // Signal for removal
|
||||||
|
}
|
||||||
|
|
||||||
|
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(uid)
|
||||||
|
properties[AttestationConstants.TAG_VENDOR_PATCHLEVEL] =
|
||||||
|
if (vendorPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
ASN1Integer(vendorPatch.toLong()),
|
||||||
),
|
)
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL to
|
} else {
|
||||||
|
null // Signal for removal
|
||||||
|
}
|
||||||
|
|
||||||
|
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid)
|
||||||
|
properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] =
|
||||||
|
if (bootPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
ASN1Integer(bootPatch.toLong()),
|
||||||
),
|
)
|
||||||
)
|
} else {
|
||||||
|
null // Signal for removal
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
||||||
@@ -103,7 +132,7 @@ object AttestationBuilder {
|
|||||||
uid: Int,
|
uid: Int,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): ASN1Sequence {
|
): ASN1Sequence {
|
||||||
val teeEnforced = buildTeeEnforcedList(params, securityLevel)
|
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
|
||||||
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
|
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
|
||||||
|
|
||||||
val fields =
|
val fields =
|
||||||
@@ -125,7 +154,11 @@ object AttestationBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
||||||
private fun buildTeeEnforcedList(params: KeyMintAttestation, securityLevel: Int): DERSequence {
|
private fun buildTeeEnforcedList(
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): DERSequence {
|
||||||
val list =
|
val list =
|
||||||
mutableListOf<ASN1Encodable>(
|
mutableListOf<ASN1Encodable>(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -164,28 +197,12 @@ object AttestationBuilder {
|
|||||||
AttestationConstants.TAG_ROOT_OF_TRUST,
|
AttestationConstants.TAG_ROOT_OF_TRUST,
|
||||||
buildRootOfTrust(null),
|
buildRootOfTrust(null),
|
||||||
),
|
),
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
|
||||||
|
val simulatedProperties = getSimulatedHardwareProperties(uid)
|
||||||
|
simulatedProperties.values.filterNotNull().forEach { list.add(it) }
|
||||||
|
|
||||||
// Add optional device identifiers if they were provided.
|
// Add optional device identifiers if they were provided.
|
||||||
params.brand?.let {
|
params.brand?.let {
|
||||||
list.add(
|
list.add(
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ object AttestationPatcher {
|
|||||||
parsedAttestation,
|
parsedAttestation,
|
||||||
keybox,
|
keybox,
|
||||||
originalLeaf.sigAlgName,
|
originalLeaf.sigAlgName,
|
||||||
|
uid,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
||||||
@@ -90,6 +91,7 @@ object AttestationPatcher {
|
|||||||
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
|
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
|
||||||
* certificate. This is required to ensure the new certificate is signed using a compatible
|
* certificate. This is required to ensure the new certificate is signed using a compatible
|
||||||
* algorithm.
|
* algorithm.
|
||||||
|
* @param uid The UID of the application requesting the certificate.
|
||||||
* @return A new [Certificate] object.
|
* @return A new [Certificate] object.
|
||||||
*/
|
*/
|
||||||
private fun createPatchedLeafCertificate(
|
private fun createPatchedLeafCertificate(
|
||||||
@@ -97,6 +99,7 @@ object AttestationPatcher {
|
|||||||
parsedAttestation: ParsedAttestation,
|
parsedAttestation: ParsedAttestation,
|
||||||
keybox: KeyBox,
|
keybox: KeyBox,
|
||||||
sigAlgName: String,
|
sigAlgName: String,
|
||||||
|
uid: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
||||||
// chain.
|
// chain.
|
||||||
@@ -113,7 +116,7 @@ object AttestationPatcher {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create the new, patched attestation extension.
|
// Create the new, patched attestation extension.
|
||||||
val patchedExtension = createPatchedAttestationExtension(parsedAttestation)
|
val patchedExtension = createPatchedAttestationExtension(parsedAttestation, uid)
|
||||||
|
|
||||||
// Copy all other extensions from the original certificate, except for the attestation.
|
// Copy all other extensions from the original certificate, except for the attestation.
|
||||||
originalLeafHolder.extensions.extensionOIDs.forEach {
|
originalLeafHolder.extensions.extensionOIDs.forEach {
|
||||||
@@ -199,7 +202,7 @@ object AttestationPatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new, patched attestation extension using simulated device properties. */
|
/** Constructs a new, patched attestation extension using simulated device properties. */
|
||||||
private fun createPatchedAttestationExtension(parsed: ParsedAttestation): Extension {
|
private fun createPatchedAttestationExtension(parsed: ParsedAttestation, uid: Int): Extension {
|
||||||
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
|
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
|
||||||
|
|
||||||
var formattedString = allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
var formattedString = allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||||
@@ -210,8 +213,19 @@ object AttestationPatcher {
|
|||||||
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
|
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
|
||||||
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
||||||
|
|
||||||
// Add other simulated hardware properties.
|
// Get the desired state for simulated properties.
|
||||||
teeEnforcedMap.putAll(AttestationBuilder.getSimulatedHardwareProperties())
|
val simulatedProperties = AttestationBuilder.getSimulatedHardwareProperties(uid)
|
||||||
|
|
||||||
|
// Apply the desired state: update, add, or remove properties from the original map.
|
||||||
|
simulatedProperties.forEach { (tag, value) ->
|
||||||
|
if (value != null) {
|
||||||
|
// If the value is not null, add or update it.
|
||||||
|
teeEnforcedMap[tag] = value
|
||||||
|
} else {
|
||||||
|
// If the value is null, remove the tag from the map.
|
||||||
|
teeEnforcedMap.remove(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
|
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
|
||||||
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
|
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ object ConfigurationManager {
|
|||||||
@Volatile private var packageModes = mapOf<String, Mode>()
|
@Volatile private var packageModes = mapOf<String, Mode>()
|
||||||
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
||||||
@Volatile private var isTeeBroken: Boolean? = null
|
@Volatile private var isTeeBroken: Boolean? = null
|
||||||
@Volatile var customPatchLevelOverride: CustomPatchLevel? = null
|
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
|
||||||
|
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
|
||||||
|
|
||||||
// Cache for UID to package name resolution.
|
// Cache for UID to package name resolution.
|
||||||
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
||||||
@@ -104,6 +105,21 @@ object ConfigurationManager {
|
|||||||
return null // No configuration found for this UID.
|
return null // No configuration found for this UID.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the custom patch level configuration for a given UID. It first checks for a
|
||||||
|
* package-specific override and falls back to the global configuration.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the calling application.
|
||||||
|
* @return The applicable [CustomPatchLevel], or null if no custom configuration exists.
|
||||||
|
*/
|
||||||
|
fun getPatchLevelForUid(uid: Int): CustomPatchLevel? {
|
||||||
|
val packages = getPackagesForUid(uid)
|
||||||
|
// Find the first package-specific configuration for this UID.
|
||||||
|
val packageSpecificPatchLevel =
|
||||||
|
packages.firstNotNullOfOrNull { pkg -> packagePatchLevels[pkg] }
|
||||||
|
return packageSpecificPatchLevel ?: globalCustomPatchLevel
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
|
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
|
||||||
* each package.
|
* each package.
|
||||||
@@ -162,26 +178,48 @@ object ConfigurationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads the security patch level override configuration from `security_patch.txt`. */
|
/**
|
||||||
|
* Loads and parses the `security_patch.txt` file, which can define both global and per-package
|
||||||
|
* security patch levels.
|
||||||
|
*/
|
||||||
private fun loadPatchLevelConfig(file: File) {
|
private fun loadPatchLevelConfig(file: File) {
|
||||||
if (file.exists()) {
|
if (!file.exists()) {
|
||||||
try {
|
globalCustomPatchLevel = null
|
||||||
val lines =
|
packagePatchLevels = emptyMap()
|
||||||
file.readLines().mapNotNull { line ->
|
return
|
||||||
val trimmed = line.trim()
|
}
|
||||||
if (trimmed.isNotEmpty() && !trimmed.startsWith("#")) trimmed else null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lines.isEmpty()) {
|
try {
|
||||||
customPatchLevelOverride = null
|
val newPackageLevels = mutableMapOf<String, CustomPatchLevel>()
|
||||||
return
|
var currentContext = "" // Empty string for global context
|
||||||
}
|
val contextLines = mutableMapOf<String, MutableList<String>>()
|
||||||
|
val contextRegex = Regex("^\\[([a-zA-Z0-9_.-]+)]$")
|
||||||
|
|
||||||
|
// First pass: group lines by context (global or package-specific).
|
||||||
|
file.readLines().forEach { line ->
|
||||||
|
val trimmedLine = line.trim()
|
||||||
|
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
|
||||||
|
|
||||||
|
contextRegex.find(trimmedLine)?.let { currentContext = it.groupValues[1] }
|
||||||
|
?: run {
|
||||||
|
contextLines
|
||||||
|
.computeIfAbsent(currentContext) { mutableListOf() }
|
||||||
|
.add(trimmedLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to parse a set of lines into a CustomPatchLevel object.
|
||||||
|
fun parseLines(lines: List<String>?): CustomPatchLevel? {
|
||||||
|
if (lines.isNullOrEmpty()) return null
|
||||||
|
|
||||||
// Handle simple case: one line sets the patch level for all components.
|
// Handle simple case: one line sets the patch level for all components.
|
||||||
if (lines.size == 1 && '=' !in lines[0]) {
|
if (lines.size == 1 && '=' !in lines[0]) {
|
||||||
customPatchLevelOverride =
|
return CustomPatchLevel(
|
||||||
CustomPatchLevel(system = null, vendor = null, boot = null, all = lines[0])
|
system = null,
|
||||||
return
|
vendor = null,
|
||||||
|
boot = null,
|
||||||
|
all = lines[0],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle key-value pair configuration.
|
// Handle key-value pair configuration.
|
||||||
@@ -195,19 +233,32 @@ object ConfigurationManager {
|
|||||||
.toMap()
|
.toMap()
|
||||||
|
|
||||||
val all = map["all"]
|
val all = map["all"]
|
||||||
customPatchLevelOverride =
|
return CustomPatchLevel(
|
||||||
CustomPatchLevel(
|
system = map["system"] ?: all,
|
||||||
system = map["system"] ?: all,
|
vendor = map["vendor"] ?: all,
|
||||||
vendor = map["vendor"] ?: all,
|
boot = map["boot"] ?: all,
|
||||||
boot = map["boot"] ?: all,
|
all = all,
|
||||||
all = all,
|
)
|
||||||
)
|
|
||||||
SystemLogger.info("Loaded custom security patch levels.")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
customPatchLevelOverride = null
|
// Parse global and per-package configurations.
|
||||||
|
val newGlobalLevel = parseLines(contextLines[""])
|
||||||
|
contextLines.remove("") // Remove global context to iterate over packages next
|
||||||
|
|
||||||
|
for ((pkg, lines) in contextLines) {
|
||||||
|
parseLines(lines)?.let { newPackageLevels[pkg] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically update the configuration state.
|
||||||
|
globalCustomPatchLevel = newGlobalLevel
|
||||||
|
packagePatchLevels = newPackageLevels
|
||||||
|
|
||||||
|
SystemLogger.info(
|
||||||
|
"Loaded custom security patch levels: global config exists=${newGlobalLevel != null}, " +
|
||||||
|
"${newPackageLevels.size} package-specific configs."
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object AndroidDeviceUtils {
|
object AndroidDeviceUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constant to signify that a patch level should not be included in the attestation.
|
||||||
|
*/
|
||||||
|
internal const val DO_NOT_REPORT = -1
|
||||||
|
|
||||||
// --- Boot Key and Verified Boot Hash ---
|
// --- Boot Key and Verified Boot Hash ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,30 +162,33 @@ object AndroidDeviceUtils {
|
|||||||
|
|
||||||
// --- Patch Level Properties ---
|
// --- Patch Level Properties ---
|
||||||
|
|
||||||
val patchLevel: Int
|
fun getPatchLevel(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "system", isLong = false)
|
||||||
getCustomPatchLevelFor("system", isLong = false)
|
// If custom is null, it means 'device_default' was used, so we fall back.
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = false)
|
// Otherwise, we use the returned value, which is either the parsed date or DO_NOT_REPORT.
|
||||||
|
return custom ?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = false)
|
||||||
|
}
|
||||||
|
|
||||||
val vendorPatchLevelLong: Int
|
fun getVendorPatchLevelLong(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "vendor", isLong = true)
|
||||||
getCustomPatchLevelFor("vendor", isLong = true)
|
return custom ?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
}
|
||||||
|
|
||||||
val bootPatchLevelLong: Int
|
fun getBootPatchLevelLong(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "boot", isLong = true)
|
||||||
getCustomPatchLevelFor("boot", isLong = true)
|
return custom ?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a custom patch level from the configuration if available.
|
* Retrieves a custom patch level from the configuration if available for a specific UID.
|
||||||
*
|
*
|
||||||
|
* @param uid The UID of the calling application.
|
||||||
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
||||||
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
||||||
* @return The custom patch level, or null if not configured.
|
* @return The custom patch level, or null if not configured.
|
||||||
*/
|
*/
|
||||||
private fun getCustomPatchLevelFor(component: String, isLong: Boolean): Int? {
|
private fun getCustomPatchLevelFor(uid: Int, component: String, isLong: Boolean): Int? {
|
||||||
val config = ConfigurationManager.customPatchLevelOverride ?: return null
|
val config = ConfigurationManager.getPatchLevelForUid(uid) ?: return null
|
||||||
val value =
|
val value =
|
||||||
when (component) {
|
when (component) {
|
||||||
"system" -> config.system ?: config.all
|
"system" -> config.system ?: config.all
|
||||||
@@ -189,11 +197,14 @@ object AndroidDeviceUtils {
|
|||||||
else -> config.all
|
else -> config.all
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
// "prop" or "no" indicates falling back to the system default.
|
return when {
|
||||||
if (value.equals("no", ignoreCase = true) || value.equals("prop", ignoreCase = true)) {
|
// "device_default" indicates falling back to the system property.
|
||||||
return null
|
value.equals("device_default", ignoreCase = true) -> null
|
||||||
|
// "no" indicates this value should not be reported.
|
||||||
|
value.equals("no", ignoreCase = true) -> DO_NOT_REPORT
|
||||||
|
// Otherwise, parse the date string.
|
||||||
|
else -> parsePatchLevelValue(value, isLong)
|
||||||
}
|
}
|
||||||
return parsePatchLevelValue(value, isLong)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
||||||
|
|||||||
Reference in New Issue
Block a user