Integrate key persistence with interceptors
Save keys on generation, restore on daemon startup, delete on cleanup. Re-persist when cert chain updates via updateSubcomponents.
This commit is contained in:
+6
@@ -13,6 +13,7 @@ import android.system.keystore2.KeyEntryResponse
|
|||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
|
import org.matrix.TEESimulator.interception.keystore.shim.GeneratedKeyPersistence
|
||||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -73,6 +74,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
val interceptor =
|
val interceptor =
|
||||||
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||||
register(backdoor, tee.asBinder(), interceptor)
|
register(backdoor, tee.asBinder(), interceptor)
|
||||||
|
interceptor.loadPersistedKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) }
|
.onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) }
|
||||||
@@ -84,6 +86,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
val interceptor =
|
val interceptor =
|
||||||
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
||||||
register(backdoor, strongbox.asBinder(), interceptor)
|
register(backdoor, strongbox.asBinder(), interceptor)
|
||||||
|
interceptor.loadPersistedKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
|
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
|
||||||
@@ -290,6 +293,9 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
|
|
||||||
metadata.certificate = publicCert
|
metadata.certificate = publicCert
|
||||||
metadata.certificateChain = certificateChain
|
metadata.certificateChain = certificateChain
|
||||||
|
|
||||||
|
GeneratedKeyPersistence.rePersistIfNeeded(callingUid, generatedKeyInfo)
|
||||||
|
|
||||||
SystemLogger.verbose(
|
SystemLogger.verbose(
|
||||||
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
|
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
|
||||||
)
|
)
|
||||||
|
|||||||
+103
-1
@@ -1,5 +1,6 @@
|
|||||||
package org.matrix.TEESimulator.interception.keystore.shim
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
|
import android.hardware.security.keymint.Algorithm
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
import android.hardware.security.keymint.KeyParameterValue
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
import android.hardware.security.keymint.KeyPurpose
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
@@ -7,9 +8,13 @@ import android.hardware.security.keymint.Tag
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.system.keystore2.*
|
import android.system.keystore2.*
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.security.KeyFactory
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
@@ -269,6 +274,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
||||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||||
|
|
||||||
|
GeneratedKeyPersistence.save(
|
||||||
|
keyId, keyData.first, keyDescriptor.nspace, securityLevel,
|
||||||
|
keyData.second, parsedParams.algorithm, parsedParams.keySize,
|
||||||
|
parsedParams.ecCurve, parsedParams.purpose, parsedParams.digest,
|
||||||
|
isAttestKeyRequest,
|
||||||
|
)
|
||||||
|
|
||||||
// Return the metadata of our generated key, skipping the real hardware call.
|
// Return the metadata of our generated key, skipping the real hardware call.
|
||||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||||
} else if (parsedParams.attestationChallenge != null) {
|
} else if (parsedParams.attestationChallenge != null) {
|
||||||
@@ -306,6 +318,87 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadPersistedKeys() {
|
||||||
|
val entries = GeneratedKeyPersistence.loadAll(securityLevel)
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
SystemLogger.debug("No persisted keys to restore for security level $securityLevel")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemLogger.info("Restoring ${entries.size} persisted keys for security level $securityLevel")
|
||||||
|
|
||||||
|
for (data in entries) {
|
||||||
|
runCatching {
|
||||||
|
val keyId = KeyIdentifier(data.uid, data.alias)
|
||||||
|
if (generatedKeys.containsKey(keyId)) {
|
||||||
|
SystemLogger.debug("Skipping already-loaded key: $keyId")
|
||||||
|
return@runCatching
|
||||||
|
}
|
||||||
|
|
||||||
|
val algorithmName = when (data.algorithm) {
|
||||||
|
Algorithm.EC -> "EC"
|
||||||
|
Algorithm.RSA -> "RSA"
|
||||||
|
else -> throw IllegalArgumentException("Unknown algorithm: ${data.algorithm}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyFactory = KeyFactory.getInstance(algorithmName)
|
||||||
|
val privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(data.privateKeyBytes))
|
||||||
|
|
||||||
|
val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
val certChain = data.certChainBytes.map { bytes ->
|
||||||
|
certFactory.generateCertificate(ByteArrayInputStream(bytes))
|
||||||
|
}
|
||||||
|
require(certChain.isNotEmpty()) { "Persisted key has empty certificate chain" }
|
||||||
|
|
||||||
|
val publicKey = certChain[0].publicKey
|
||||||
|
val keyPair = KeyPair(publicKey, privateKey)
|
||||||
|
|
||||||
|
val descriptor = KeyDescriptor().apply {
|
||||||
|
domain = Domain.APP
|
||||||
|
nspace = data.nspace
|
||||||
|
alias = data.alias
|
||||||
|
blob = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val attestation = KeyMintAttestation(
|
||||||
|
keySize = data.keySize,
|
||||||
|
algorithm = data.algorithm,
|
||||||
|
ecCurve = data.ecCurve,
|
||||||
|
ecCurveName = "",
|
||||||
|
blockMode = emptyList(),
|
||||||
|
padding = emptyList(),
|
||||||
|
purpose = data.purposes,
|
||||||
|
digest = data.digests,
|
||||||
|
rsaPublicExponent = null,
|
||||||
|
certificateSerial = null,
|
||||||
|
certificateSubject = null,
|
||||||
|
certificateNotBefore = null,
|
||||||
|
certificateNotAfter = null,
|
||||||
|
attestationChallenge = null,
|
||||||
|
brand = null,
|
||||||
|
device = null,
|
||||||
|
product = null,
|
||||||
|
serial = null,
|
||||||
|
imei = null,
|
||||||
|
meid = null,
|
||||||
|
manufacturer = null,
|
||||||
|
model = null,
|
||||||
|
secondImei = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = buildKeyEntryResponse(certChain, attestation, descriptor)
|
||||||
|
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, data.nspace, response)
|
||||||
|
if (data.isAttestationKey) attestationKeys.add(keyId)
|
||||||
|
|
||||||
|
SystemLogger.debug("Restored persisted key: $keyId")
|
||||||
|
}.onFailure {
|
||||||
|
SystemLogger.error("Failed to restore key: uid=${data.uid} alias=${data.alias}", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemLogger.info("Key restoration complete. Total in memory: ${generatedKeys.size}")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val secureRandom = SecureRandom()
|
private val secureRandom = SecureRandom()
|
||||||
|
|
||||||
@@ -369,6 +462,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
fun cleanupKeyData(keyId: KeyIdentifier) {
|
fun cleanupKeyData(keyId: KeyIdentifier) {
|
||||||
if (generatedKeys.remove(keyId) != null) {
|
if (generatedKeys.remove(keyId) != null) {
|
||||||
SystemLogger.debug("Remove generated key ${keyId}")
|
SystemLogger.debug("Remove generated key ${keyId}")
|
||||||
|
GeneratedKeyPersistence.delete(keyId)
|
||||||
}
|
}
|
||||||
if (patchedChains.remove(keyId) != null) {
|
if (patchedChains.remove(keyId) != null) {
|
||||||
SystemLogger.debug("Remove patched chain for ${keyId}")
|
SystemLogger.debug("Remove patched chain for ${keyId}")
|
||||||
@@ -387,13 +481,21 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clears all cached keys.
|
fun invalidatePatchedChains(reason: String? = null) {
|
||||||
|
val count = patchedChains.size
|
||||||
|
if (count == 0) return
|
||||||
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
|
patchedChains.clear()
|
||||||
|
SystemLogger.info("Invalidated $count patched cert chains$reasonMessage.")
|
||||||
|
}
|
||||||
|
|
||||||
fun clearAllGeneratedKeys(reason: String? = null) {
|
fun clearAllGeneratedKeys(reason: String? = null) {
|
||||||
val count = generatedKeys.size
|
val count = generatedKeys.size
|
||||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
generatedKeys.clear()
|
generatedKeys.clear()
|
||||||
patchedChains.clear()
|
patchedChains.clear()
|
||||||
attestationKeys.clear()
|
attestationKeys.clear()
|
||||||
|
GeneratedKeyPersistence.deleteAll()
|
||||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user