fix(attestation): align authorization list and cert extension with AOSP keystore2 semantics
toAuthorizations() was missing OS_VERSION, OS_PATCHLEVEL, VENDOR_PATCHLEVEL, BOOT_PATCHLEVEL, CREATION_DATETIME, USER_ID, PADDING, and RSA_PUBLIC_EXPONENT tags that real TEE-generated KeyMetadata always includes. EC_CURVE was also hardcoded unconditionally, producing invalid authorizations for RSA keys. Additionally, live-patched certificate chains in getKeyEntry weren't cached, causing re-patching on every call with potentially different signatures. Ports upstream JingMatrix/TEESimulator#148 and #150.
This commit is contained in:
@@ -182,11 +182,36 @@ object AttestationBuilder {
|
|||||||
AttestationConstants.TAG_DIGEST,
|
AttestationConstants.TAG_DIGEST,
|
||||||
DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
|
DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (params.ecCurve != null) {
|
||||||
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_EC_CURVE,
|
AttestationConstants.TAG_EC_CURVE,
|
||||||
ASN1Integer(params.ecCurve.toLong()),
|
ASN1Integer(params.ecCurve.toLong()),
|
||||||
),
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.padding.forEach {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_PADDING, ASN1Integer(it.toLong()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.rsaPublicExponent != null) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_RSA_PUBLIC_EXPONENT,
|
||||||
|
ASN1Integer(params.rsaPublicExponent.toLong()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
list.addAll(
|
||||||
|
listOf(
|
||||||
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
|
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
@@ -199,6 +224,7 @@ object AttestationBuilder {
|
|||||||
buildRootOfTrust(null),
|
buildRootOfTrust(null),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
|
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
|
||||||
val simulatedProperties = getSimulatedHardwareProperties(uid)
|
val simulatedProperties = getSimulatedHardwareProperties(uid)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
|||||||
data class KeyMintAttestation(
|
data class KeyMintAttestation(
|
||||||
val keySize: Int,
|
val keySize: Int,
|
||||||
val algorithm: Int,
|
val algorithm: Int,
|
||||||
val ecCurve: Int,
|
val ecCurve: Int?,
|
||||||
val ecCurveName: String,
|
val ecCurveName: String,
|
||||||
val origin: Int?,
|
val origin: Int?,
|
||||||
val blockMode: List<Int>,
|
val blockMode: List<Int>,
|
||||||
@@ -53,7 +53,7 @@ data class KeyMintAttestation(
|
|||||||
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
|
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
|
||||||
|
|
||||||
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
|
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
|
||||||
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
|
ecCurve = params.findEcCurve(Tag.EC_CURVE),
|
||||||
ecCurveName = params.deriveEcCurveName(),
|
ecCurveName = params.deriveEcCurveName(),
|
||||||
|
|
||||||
// AOSP: [key_param(tag = ORIGIN, field = Origin)]
|
// AOSP: [key_param(tag = ORIGIN, field = Origin)]
|
||||||
|
|||||||
+2
-1
@@ -305,7 +305,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
certChain = keyData.second,
|
certChain = keyData.second,
|
||||||
algorithm = parsedParameters.algorithm,
|
algorithm = parsedParameters.algorithm,
|
||||||
keySize = parsedParameters.keySize,
|
keySize = parsedParameters.keySize,
|
||||||
ecCurve = parsedParameters.ecCurve,
|
ecCurve = parsedParameters.ecCurve ?: 0,
|
||||||
purposes = parsedParameters.purpose,
|
purposes = parsedParameters.purpose,
|
||||||
digests = parsedParameters.digest,
|
digests = parsedParameters.digest,
|
||||||
isAttestationKey = true,
|
isAttestationKey = true,
|
||||||
@@ -337,6 +337,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
)
|
)
|
||||||
finalChain =
|
finalChain =
|
||||||
AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||||
|
KeyMintSecurityLevelInterceptor.patchedChains[keyId] = finalChain
|
||||||
}
|
}
|
||||||
|
|
||||||
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
|
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
|
||||||
|
|||||||
+36
-17
@@ -333,7 +333,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
} ?: throw Exception("Both native and BouncyCastle cert gen failed.")
|
} ?: throw Exception("Both native and BouncyCastle cert gen failed.")
|
||||||
|
|
||||||
cleanupKeyData(keyId)
|
cleanupKeyData(keyId)
|
||||||
val response = buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
|
val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
||||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||||
|
|
||||||
@@ -345,7 +345,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
certChain = keyData.second.toList(),
|
certChain = keyData.second.toList(),
|
||||||
algorithm = parsedParams.algorithm,
|
algorithm = parsedParams.algorithm,
|
||||||
keySize = parsedParams.keySize,
|
keySize = parsedParams.keySize,
|
||||||
ecCurve = parsedParams.ecCurve,
|
ecCurve = parsedParams.ecCurve ?: 0,
|
||||||
purposes = parsedParams.purpose,
|
purposes = parsedParams.purpose,
|
||||||
digests = parsedParams.digest,
|
digests = parsedParams.digest,
|
||||||
isAttestationKey = isAttestKeyRequest,
|
isAttestationKey = isAttestKeyRequest,
|
||||||
@@ -383,7 +383,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val config = CertGenConfig(
|
val config = CertGenConfig(
|
||||||
algorithm = params.algorithm,
|
algorithm = params.algorithm,
|
||||||
keySize = params.keySize,
|
keySize = params.keySize,
|
||||||
ecCurve = params.ecCurve,
|
ecCurve = params.ecCurve ?: 0,
|
||||||
rsaPublicExponent = params.rsaPublicExponent?.toLong() ?: 65537L,
|
rsaPublicExponent = params.rsaPublicExponent?.toLong() ?: 65537L,
|
||||||
attestationChallenge = params.attestationChallenge,
|
attestationChallenge = params.attestationChallenge,
|
||||||
purposes = params.purpose.toIntArray(),
|
purposes = params.purpose.toIntArray(),
|
||||||
@@ -427,6 +427,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildKeyEntryResponse(
|
private fun buildKeyEntryResponse(
|
||||||
|
callingUid: Int,
|
||||||
chain: List<Certificate>,
|
chain: List<Certificate>,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
descriptor: KeyDescriptor,
|
descriptor: KeyDescriptor,
|
||||||
@@ -443,7 +444,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
keySecurityLevel = securityLevel
|
keySecurityLevel = securityLevel
|
||||||
key = normalizedKeyDescriptor
|
key = normalizedKeyDescriptor
|
||||||
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
|
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
|
||||||
authorizations = params.toAuthorizations(securityLevel)
|
authorizations = params.toAuthorizations(callingUid, securityLevel)
|
||||||
modificationTimeMs = System.currentTimeMillis()
|
modificationTimeMs = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
return KeyEntryResponse().apply {
|
return KeyEntryResponse().apply {
|
||||||
@@ -521,7 +522,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
secondImei = null,
|
secondImei = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = buildKeyEntryResponse(certChain, attestation, descriptor)
|
val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response)
|
||||||
if (record.isAttestationKey) attestationKeys.add(keyId)
|
if (record.isAttestationKey) attestationKeys.add(keyId)
|
||||||
|
|
||||||
@@ -605,8 +606,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||||
// Caches patched chains to prevent re-generation and signature inconsistencies
|
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
|
||||||
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
||||||
|
|
||||||
@@ -666,7 +666,10 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Authorization> {
|
private fun KeyMintAttestation.toAuthorizations(
|
||||||
|
callingUid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): Array<Authorization> {
|
||||||
val authList = mutableListOf<Authorization>()
|
val authList = mutableListOf<Authorization>()
|
||||||
|
|
||||||
fun createAuth(tag: Int, value: KeyParameterValue): Authorization {
|
fun createAuth(tag: Int, value: KeyParameterValue): Authorization {
|
||||||
@@ -681,19 +684,35 @@ private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Autho
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
|
||||||
|
if (this.ecCurve != null) {
|
||||||
|
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
||||||
|
}
|
||||||
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
||||||
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
|
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
|
||||||
|
this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) }
|
||||||
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
|
|
||||||
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
||||||
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
if (this.rsaPublicExponent != null) {
|
||||||
authList.add(
|
authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
|
||||||
createAuth(
|
}
|
||||||
Tag.ORIGIN,
|
|
||||||
KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
|
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||||
|
authList.add(createAuth(Tag.ORIGIN, KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED)))
|
||||||
|
authList.add(createAuth(Tag.OS_VERSION, KeyParameterValue.integer(AndroidDeviceUtils.osVersion)))
|
||||||
|
|
||||||
|
val osPatch = AndroidDeviceUtils.getPatchLevel(callingUid)
|
||||||
|
if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
|
||||||
|
authList.add(createAuth(Tag.OS_PATCHLEVEL, KeyParameterValue.integer(osPatch)))
|
||||||
|
}
|
||||||
|
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
|
||||||
|
if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
|
||||||
|
authList.add(createAuth(Tag.VENDOR_PATCHLEVEL, KeyParameterValue.integer(vendorPatch)))
|
||||||
|
}
|
||||||
|
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
|
||||||
|
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
|
||||||
|
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
|
||||||
|
}
|
||||||
|
authList.add(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
|
||||||
|
authList.add(createAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
|
||||||
|
|
||||||
return authList.toTypedArray()
|
return authList.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user