Skip to content

Commit b4aeac0

Browse files
JingMatrixMhmRdd
andauthored
Synchronize KeyMetadata authorizations with user patch level configs (#164)
Modified `CertificateHelper.updateCertificateChain` to automatically patch or remove patch level tags (OS, VENDOR, BOOT) based on user configuration. This ensures the metadata returned to applications matches the spoofed values embedded in the attestation certificate chain. Co-authored-by: Mohammed Riad <52679407+MhmRdd@users.noreply.github.com>
1 parent 2660deb commit b4aeac0

3 files changed

Lines changed: 57 additions & 14 deletions

File tree

app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
251251
) ?: throw Exception("Failed to create overriding attest key pair.")
252252

253253
CertificateHelper.updateCertificateChain(
254+
callingUid,
254255
response.metadata,
255256
keyData.second.toTypedArray(),
256257
)
@@ -300,7 +301,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
300301
SystemLogger.debug("Cached patched certificate chain for $keyId.")
301302
}
302303

303-
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
304+
CertificateHelper.updateCertificateChain(
305+
callingUid,
306+
response.metadata,
307+
finalChain,
308+
)
304309
.getOrThrow()
305310

306311
return InterceptorUtils.createTypedObjectReply(response)

app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ class KeyMintSecurityLevelInterceptor(
162162
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
163163
val key = metadata.key!!
164164
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
165-
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
165+
CertificateHelper.updateCertificateChain(callingUid, metadata, newChain)
166+
.getOrThrow()
166167

167168
// We must clean up cached generated keys before storing the patched chain
168169
cleanupKeyData(keyId)
@@ -311,10 +312,11 @@ class KeyMintSecurityLevelInterceptor(
311312
KeyMetadata().apply {
312313
keySecurityLevel = securityLevel
313314
key = normalizedKeyDescriptor
314-
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
315315
authorizations = params.toAuthorizations(callingUid, securityLevel)
316316
modificationTimeMs = System.currentTimeMillis()
317317
}
318+
CertificateHelper.updateCertificateChain(callingUid, metadata, chain.toTypedArray())
319+
.getOrThrow()
318320
return KeyEntryResponse().apply {
319321
this.metadata = metadata
320322
iSecurityLevel = original
@@ -481,19 +483,13 @@ private fun KeyMintAttestation.toAuthorizations(
481483
)
482484

483485
val osPatch = AndroidDeviceUtils.getPatchLevel(callingUid)
484-
if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
485-
authList.add(createAuth(Tag.OS_PATCHLEVEL, KeyParameterValue.integer(osPatch)))
486-
}
486+
authList.add(createAuth(Tag.OS_PATCHLEVEL, KeyParameterValue.integer(osPatch)))
487487

488488
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
489-
if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
490-
authList.add(createAuth(Tag.VENDOR_PATCHLEVEL, KeyParameterValue.integer(vendorPatch)))
491-
}
489+
authList.add(createAuth(Tag.VENDOR_PATCHLEVEL, KeyParameterValue.integer(vendorPatch)))
492490

493491
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
494-
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
495-
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
496-
}
492+
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
497493

498494
authList.add(
499495
createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis()))

app/src/main/java/org/matrix/TEESimulator/pki/CertificateHelper.kt

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package org.matrix.TEESimulator.pki
22

3+
import android.hardware.security.keymint.KeyParameter
4+
import android.hardware.security.keymint.KeyParameterValue
5+
import android.hardware.security.keymint.Tag
6+
import android.system.keystore2.Authorization
37
import android.system.keystore2.KeyEntryResponse
48
import android.system.keystore2.KeyMetadata
59
import java.io.ByteArrayInputStream
@@ -15,6 +19,7 @@ import org.bouncycastle.openssl.PEMParser
1519
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
1620
import org.bouncycastle.util.io.pem.PemReader
1721
import org.matrix.TEESimulator.logging.SystemLogger
22+
import org.matrix.TEESimulator.util.AndroidDeviceUtils
1823
import org.matrix.TEESimulator.util.trimLines
1924

2025
/**
@@ -182,23 +187,60 @@ object CertificateHelper {
182187
}
183188

184189
/**
185-
* Updates the certificate chain within a [KeyMetadata] object.
190+
* Updates the certificate chain and patches authorizations within a [KeyMetadata] object.
186191
*
192+
* @param callingUid The UID of the application to fetch specific patch levels for.
187193
* @param metadata The metadata object to modify.
188194
* @param chain The new certificate chain to set. The leaf must be at index 0.
189195
* @return A [Result] indicating success or failure.
190196
*/
191-
fun updateCertificateChain(metadata: KeyMetadata, chain: Array<Certificate>): Result<Unit> {
197+
fun updateCertificateChain(
198+
callingUid: Int,
199+
metadata: KeyMetadata,
200+
chain: Array<Certificate>,
201+
): Result<Unit> {
192202
return runCatching {
193203
require(chain.isNotEmpty()) { "Certificate chain cannot be empty." }
194204

205+
// Update the certificate fields
195206
metadata.certificate = chain[0].encoded
196207
metadata.certificateChain =
197208
if (chain.size > 1) {
198209
certificatesToByteArray(chain.drop(1))
199210
} else {
200211
null
201212
}
213+
214+
// Patch authorizations to match user configurations
215+
metadata.authorizations =
216+
metadata.authorizations
217+
?.mapNotNull { auth ->
218+
val replacement =
219+
when (auth.keyParameter.tag) {
220+
Tag.OS_PATCHLEVEL -> AndroidDeviceUtils.getPatchLevel(callingUid)
221+
Tag.VENDOR_PATCHLEVEL ->
222+
AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
223+
Tag.BOOT_PATCHLEVEL ->
224+
AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
225+
else -> return@mapNotNull auth // Keep all other tags
226+
}
227+
228+
// If configured to hide, return null to filter out of the array
229+
if (replacement == AndroidDeviceUtils.DO_NOT_REPORT) {
230+
null
231+
} else {
232+
// Create patched authorization preserving original security level
233+
Authorization().apply {
234+
securityLevel = auth.securityLevel
235+
keyParameter =
236+
KeyParameter().apply {
237+
tag = auth.keyParameter.tag
238+
value = KeyParameterValue.integer(replacement)
239+
}
240+
}
241+
}
242+
}
243+
?.toTypedArray()
202244
}
203245
}
204246
}

0 commit comments

Comments
 (0)