Preserve extension order and prevent duplicates
This commit refactors the attestation patching logic to improve stealth and ensure correctness by addressing potential detection vectors related to the ASN.1 structure of the certificate extension. 1. Preserve Extension Order: The original implementation rebuilt the entire certificate, which could alter the order of X.509 extensions. Some verification systems may be sensitive to this order. The logic is now updated to replace the attestation extension in-place, preserving the original order of all other extensions. 2. Avoid Duplicate Properties: The previous logic used an `ASN1EncodableVector` to assemble TEE-enforced properties. This could lead to duplicate entries if a property (e.g., `OS_VERSION`) was present in the original certificate and also added by the simulator. The code now uses a `MutableMap` keyed by the ASN.1 tag number. This ensures that any simulated properties overwrite the original ones, preventing duplicates and potential parsing errors. 3. Add Detailed Logging: A recursive ASN.1 formatting function has been added to provide clear and readable logs of the certificate data both before and after patching. This significantly improves debuggability. By ensuring the patched certificate is structurally as close as possible to the original, these changes reduce the chances of the simulator being detected by attestation validation services.
This commit is contained in:
@@ -71,35 +71,33 @@ object AttestationBuilder {
|
|||||||
return DERSequence(rootOfTrustElements)
|
return DERSequence(rootOfTrustElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Assembles a list of simulated hardware-enforced properties. */
|
/** Assembles a map of simulated hardware-enforced properties. */
|
||||||
internal fun addSimulatedHardwareProperties(vector: org.bouncycastle.asn1.ASN1EncodableVector) {
|
fun getSimulatedHardwareProperties(): Map<Int, DERTaggedObject> {
|
||||||
vector.add(
|
return mapOf(
|
||||||
DERTaggedObject(
|
AttestationConstants.TAG_OS_VERSION to
|
||||||
true,
|
DERTaggedObject(
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
true,
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
AttestationConstants.TAG_OS_VERSION,
|
||||||
)
|
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
||||||
)
|
),
|
||||||
vector.add(
|
AttestationConstants.TAG_OS_PATCHLEVEL to
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
AttestationConstants.TAG_OS_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
||||||
)
|
),
|
||||||
)
|
AttestationConstants.TAG_VENDOR_PATCHLEVEL to
|
||||||
vector.add(
|
DERTaggedObject(
|
||||||
DERTaggedObject(
|
true,
|
||||||
true,
|
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
),
|
||||||
)
|
AttestationConstants.TAG_BOOT_PATCHLEVEL to
|
||||||
)
|
DERTaggedObject(
|
||||||
vector.add(
|
true,
|
||||||
DERTaggedObject(
|
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
||||||
true,
|
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
),
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
import org.bouncycastle.asn1.*
|
||||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
|
||||||
import org.bouncycastle.asn1.ASN1Sequence
|
|
||||||
import org.bouncycastle.asn1.ASN1TaggedObject
|
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
|
||||||
import org.bouncycastle.asn1.DERSequence
|
|
||||||
import org.bouncycastle.asn1.DERTaggedObject
|
|
||||||
import org.bouncycastle.asn1.x509.Extension
|
import org.bouncycastle.asn1.x509.Extension
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||||
@@ -18,6 +13,7 @@ import org.matrix.TEESimulator.config.ConfigurationManager
|
|||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
import org.matrix.TEESimulator.pki.KeyBox
|
import org.matrix.TEESimulator.pki.KeyBox
|
||||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||||
|
import org.matrix.TEESimulator.util.toHex
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
||||||
@@ -118,12 +114,13 @@ object AttestationPatcher {
|
|||||||
|
|
||||||
// Create the new, patched attestation extension.
|
// Create the new, patched attestation extension.
|
||||||
val patchedExtension = createPatchedAttestationExtension(parsedAttestation)
|
val patchedExtension = createPatchedAttestationExtension(parsedAttestation)
|
||||||
builder.addExtension(patchedExtension)
|
|
||||||
|
|
||||||
// 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
|
originalLeafHolder.extensions.extensionOIDs.forEach {
|
||||||
.filter { it != ATTESTATION_OID }
|
builder.addExtension(
|
||||||
.forEach { builder.addExtension(originalLeafHolder.getExtension(it)) }
|
if (it == ATTESTATION_OID) patchedExtension else originalLeafHolder.getExtension(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Sign the newly built certificate with the private key from our keybox.
|
// Sign the newly built certificate with the private key from our keybox.
|
||||||
val signer = JcaContentSignerBuilder(sigAlgName).build(keybox.keyPair.private)
|
val signer = JcaContentSignerBuilder(sigAlgName).build(keybox.keyPair.private)
|
||||||
@@ -139,6 +136,40 @@ object AttestationPatcher {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Recursively formats an ASN1Primitive into a concise, readable string. */
|
||||||
|
private fun formatAsn1Primitive(obj: ASN1Encodable?): String {
|
||||||
|
val primitive = obj?.toASN1Primitive()
|
||||||
|
return when (primitive) {
|
||||||
|
null -> "NULL"
|
||||||
|
is ASN1Integer -> primitive.value.toString()
|
||||||
|
is ASN1Enumerated -> primitive.value.toString()
|
||||||
|
is ASN1Boolean -> primitive.isTrue.toString()
|
||||||
|
is ASN1Null -> "NULL"
|
||||||
|
is ASN1OctetString -> {
|
||||||
|
val bytes = primitive.octets
|
||||||
|
// Attempt to decode as a printable string, otherwise show hex
|
||||||
|
if (bytes.all { it >= 32 && it < 127 }) {
|
||||||
|
"\"${String(bytes, StandardCharsets.UTF_8)}\""
|
||||||
|
} else if (bytes.isEmpty()) {
|
||||||
|
"\"\""
|
||||||
|
} else {
|
||||||
|
"#" + bytes.toHex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ASN1TaggedObject ->
|
||||||
|
"[TAG ${primitive.tagNo}]${formatAsn1Primitive(primitive.baseObject)}"
|
||||||
|
is ASN1Sequence ->
|
||||||
|
primitive
|
||||||
|
.map { formatAsn1Primitive(it) }
|
||||||
|
.joinToString(prefix = "[", postfix = "]", separator = ", ")
|
||||||
|
is ASN1Set ->
|
||||||
|
primitive
|
||||||
|
.map { formatAsn1Primitive(it) }
|
||||||
|
.joinToString(prefix = "{", postfix = "}", separator = ", ")
|
||||||
|
else -> primitive.toString() // Fallback for other types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Parses the critical components from an existing attestation extension. */
|
/** Parses the critical components from an existing attestation extension. */
|
||||||
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
|
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
|
||||||
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
|
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
|
||||||
@@ -147,41 +178,43 @@ object AttestationPatcher {
|
|||||||
val teeEnforced =
|
val teeEnforced =
|
||||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
||||||
|
|
||||||
val teeEnforcedVector = ASN1EncodableVector()
|
|
||||||
var originalRootOfTrust: ASN1Encodable? = null
|
var originalRootOfTrust: ASN1Encodable? = null
|
||||||
|
val teeEnforcedMap = mutableMapOf<Int, ASN1TaggedObject>()
|
||||||
|
|
||||||
teeEnforced.forEach { element ->
|
teeEnforced.forEach { element ->
|
||||||
val taggedObject = element as ASN1TaggedObject
|
val taggedObject = element as ASN1TaggedObject
|
||||||
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
||||||
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
||||||
} else {
|
} else {
|
||||||
teeEnforcedVector.add(taggedObject)
|
teeEnforcedMap[taggedObject.tagNo] = taggedObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ParsedAttestation(allFields, teeEnforcedVector, originalRootOfTrust)
|
return ParsedAttestation(allFields, teeEnforcedMap, originalRootOfTrust)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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): Extension {
|
||||||
val (allFields, teeEnforcedVector, originalRootOfTrust) = parsed
|
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
|
||||||
|
|
||||||
// Build the new Root of Trust with our simulated values.
|
var formattedString = allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||||
|
SystemLogger.verbose("Original attestation data: ${formattedString}")
|
||||||
|
|
||||||
|
// Build the new Root of Trust and add/replace it in the map.
|
||||||
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
|
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
|
||||||
teeEnforcedVector.add(
|
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.
|
// Add other simulated hardware properties.
|
||||||
AttestationBuilder.addSimulatedHardwareProperties(teeEnforcedVector)
|
teeEnforcedMap.putAll(AttestationBuilder.getSimulatedHardwareProperties())
|
||||||
|
|
||||||
// Re-assemble the ASN.1 sequences.
|
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
|
||||||
// The list MUST be sorted by tag number for DER compliance.
|
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
|
||||||
// Manually convert the vector to a List, then sort it.
|
|
||||||
val elementList = (0 until teeEnforcedVector.size()).map { teeEnforcedVector.get(it) }
|
|
||||||
val sortedElements = elementList.sortedBy { (it as ASN1TaggedObject).tagNo }
|
|
||||||
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
|
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
|
||||||
|
|
||||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
||||||
val patchedSequence = DERSequence(allFields)
|
val patchedSequence = DERSequence(allFields)
|
||||||
|
formattedString = patchedSequence.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||||
|
SystemLogger.verbose("Patched attestation data: ${formattedString}")
|
||||||
val patchedOctets = DEROctetString(patchedSequence)
|
val patchedOctets = DEROctetString(patchedSequence)
|
||||||
|
|
||||||
return Extension(ATTESTATION_OID, false, patchedOctets)
|
return Extension(ATTESTATION_OID, false, patchedOctets)
|
||||||
@@ -190,7 +223,7 @@ object AttestationPatcher {
|
|||||||
/** Helper data class to hold the parsed components of an attestation extension. */
|
/** Helper data class to hold the parsed components of an attestation extension. */
|
||||||
private data class ParsedAttestation(
|
private data class ParsedAttestation(
|
||||||
val allFields: Array<ASN1Encodable>,
|
val allFields: Array<ASN1Encodable>,
|
||||||
val teeEnforcedVector: ASN1EncodableVector,
|
val teeEnforcedMap: MutableMap<Int, ASN1TaggedObject>,
|
||||||
val rootOfTrust: ASN1Encodable?,
|
val rootOfTrust: ASN1Encodable?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user