feat(interception): close remaining PR #157 compliance gaps
Full diff analysis against upstream's 50 commits revealed 8 functional gaps after v5.0. These are detectable by conformance tests or detector apps inspecting KeyMetadata authorizations and operation semantics. KeyMetadata authorizations: - Add 9 TEE-enforced tags (CALLER_NONCE, MIN_MAC_LENGTH, ROLLBACK_RESISTANCE, EARLY_BOOT_ONLY, ALLOW_WHILE_ON_BODY, TRUSTED_USER_PRESENCE_REQUIRED, TRUSTED_CONFIRMATION_REQUIRED, MAX_USES_PER_BOOT, MAX_BOOT_LEVEL) - Fix CREATION_DATETIME to SOFTWARE security level via createSwAuth - Add SOFTWARE-enforced date enforcement, USAGE_COUNT_LIMIT, UNLOCKED_DEVICE_REQUIRED Symmetric key support: - Generate AES/HMAC keys in software via javax.crypto.KeyGenerator - GeneratedKeyInfo expanded with nullable keyPair + secretKey fields - CipherPrimitive accepts java.security.Key for symmetric operations - SoftwareOperation routes ENCRYPT/DECRYPT to secretKey when available Operation compliance: - beginParameters property replaces manual IV wrapping for GCM - KeyAgreementPrimitive for ECDH AGREE_KEY operations - handleCreateOperation wrapped in runCatching (crash prevention) - SECURE_HW_COMMUNICATION_FAILED on software gen failure Certificate patching: - Import key cert chain + authorization patching in onPostTransact - patchAuthorizations added to post-generateKey PATCH mode path
This commit is contained in:
+1
@@ -364,6 +364,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
||||||
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
||||||
keyData.first,
|
keyData.first,
|
||||||
|
null,
|
||||||
keyDescriptor.nspace,
|
keyDescriptor.nspace,
|
||||||
response,
|
response,
|
||||||
parsedParameters,
|
parsedParameters,
|
||||||
|
|||||||
+2
-1
@@ -303,9 +303,10 @@ object GeneratedKeyPersistence {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val keyPair = generatedKeyInfo.keyPair ?: return
|
||||||
save(
|
save(
|
||||||
keyId = keyId,
|
keyId = keyId,
|
||||||
keyPair = generatedKeyInfo.keyPair,
|
keyPair = keyPair,
|
||||||
nspace = generatedKeyInfo.nspace,
|
nspace = generatedKeyInfo.nspace,
|
||||||
securityLevel = secLevel,
|
securityLevel = secLevel,
|
||||||
certChain = newChain.toList(),
|
certChain = newChain.toList(),
|
||||||
|
|||||||
+145
-40
@@ -46,7 +46,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
) : BinderInterceptor() {
|
) : BinderInterceptor() {
|
||||||
|
|
||||||
data class GeneratedKeyInfo(
|
data class GeneratedKeyInfo(
|
||||||
val keyPair: KeyPair,
|
val keyPair: KeyPair?,
|
||||||
|
val secretKey: javax.crypto.SecretKey?,
|
||||||
val nspace: Long,
|
val nspace: Long,
|
||||||
val response: KeyEntryResponse,
|
val response: KeyEntryResponse,
|
||||||
val keyParams: KeyMintAttestation? = null,
|
val keyParams: KeyMintAttestation? = null,
|
||||||
@@ -136,6 +137,22 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
attestationKeys.remove(keyId)
|
attestationKeys.remove(keyId)
|
||||||
importedKeys.add(keyId)
|
importedKeys.add(keyId)
|
||||||
|
|
||||||
|
if (!ConfigurationManager.shouldSkipUid(callingUid)) {
|
||||||
|
val metadata: KeyMetadata =
|
||||||
|
reply.readTypedObject(KeyMetadata.CREATOR)
|
||||||
|
?: return TransactionResult.SkipTransaction
|
||||||
|
val originalChain = CertificateHelper.getCertificateChain(metadata)
|
||||||
|
if (originalChain != null && originalChain.size > 1) {
|
||||||
|
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||||
|
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
||||||
|
metadata.authorizations =
|
||||||
|
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
|
||||||
|
patchedChains[keyId] = newChain
|
||||||
|
SystemLogger.debug("Cached patched certificate chain for imported key $keyId.")
|
||||||
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (code == CREATE_OPERATION_TRANSACTION) {
|
} else if (code == CREATE_OPERATION_TRANSACTION) {
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
@@ -189,6 +206,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val key = metadata.key!!
|
val key = metadata.key!!
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
||||||
|
metadata.authorizations =
|
||||||
|
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
|
||||||
|
|
||||||
// We must clean up cached generated keys before storing the patched chain
|
// We must clean up cached generated keys before storing the patched chain
|
||||||
cleanupKeyData(keyId)
|
cleanupKeyData(keyId)
|
||||||
@@ -237,7 +256,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
txId: Long,
|
txId: Long,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult = runCatching {
|
||||||
SystemLogger.debug("[TX_ID: $txId] createOperation parcel: dataSize=${data.dataSize()} dataAvail=${data.dataAvail()} dataPos=${data.dataPosition()}")
|
SystemLogger.debug("[TX_ID: $txId] createOperation parcel: dataSize=${data.dataSize()} dataAvail=${data.dataAvail()} dataPos=${data.dataPosition()}")
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
@@ -287,11 +306,14 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
val parsedParams = KeyMintAttestation(params).let { p ->
|
val parsedParams = KeyMintAttestation(params).let { p ->
|
||||||
if (p.algorithm != 0) p
|
if (p.algorithm != 0) p
|
||||||
else p.copy(algorithm = when (generatedKeyInfo.keyPair.private.algorithm) {
|
else {
|
||||||
"EC", "ECDSA" -> Algorithm.EC
|
val keyAlgo = generatedKeyInfo.keyPair?.private?.algorithm
|
||||||
"RSA" -> Algorithm.RSA
|
p.copy(algorithm = when (keyAlgo) {
|
||||||
else -> p.algorithm
|
"EC", "ECDSA" -> Algorithm.EC
|
||||||
})
|
"RSA" -> Algorithm.RSA
|
||||||
|
else -> generatedKeyInfo.keyParams?.algorithm ?: p.algorithm
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val forced = data.readBoolean()
|
val forced = data.readBoolean()
|
||||||
|
|
||||||
@@ -318,7 +340,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
} else parsedParams
|
} else parsedParams
|
||||||
|
|
||||||
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
|
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
|
||||||
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, effectiveParams, opLatency)
|
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, generatedKeyInfo.secretKey, effectiveParams, opLatency)
|
||||||
|
|
||||||
if (keyParams?.usageCountLimit != null) {
|
if (keyParams?.usageCountLimit != null) {
|
||||||
val limit = keyParams.usageCountLimit
|
val limit = keyParams.usageCountLimit
|
||||||
@@ -328,7 +350,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
if (remaining.get() <= 0) {
|
if (remaining.get() <= 0) {
|
||||||
cleanupKeyData(resolvedKeyId)
|
cleanupKeyData(resolvedKeyId)
|
||||||
usageCounters.remove(resolvedKeyId)
|
usageCounters.remove(resolvedKeyId)
|
||||||
throw android.os.ServiceSpecificException(RESPONSE_KEY_NOT_FOUND)
|
return InterceptorUtils.createServiceSpecificErrorReply(RESPONSE_KEY_NOT_FOUND)
|
||||||
}
|
}
|
||||||
softwareOperation.onFinishCallback = {
|
softwareOperation.onFinishCallback = {
|
||||||
if (remaining.decrementAndGet() <= 0) {
|
if (remaining.decrementAndGet() <= 0) {
|
||||||
@@ -347,19 +369,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
CreateOperationResponse().apply {
|
CreateOperationResponse().apply {
|
||||||
iOperation = operationBinder
|
iOperation = operationBinder
|
||||||
operationChallenge = null
|
operationChallenge = null
|
||||||
softwareOperation.iv?.let { iv ->
|
parameters = softwareOperation.beginParameters
|
||||||
parameters = KeyParameters().apply {
|
|
||||||
keyParameter = arrayOf(
|
|
||||||
KeyParameter().apply {
|
|
||||||
tag = Tag.NONCE
|
|
||||||
value = KeyParameterValue.blob(iv)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response)
|
InterceptorUtils.createTypedObjectReply(response)
|
||||||
|
}.getOrElse {
|
||||||
|
SystemLogger.error("Error during createOperation for UID $callingUid.", it)
|
||||||
|
InterceptorUtils.createServiceSpecificErrorReply(KEYMINT_UNKNOWN_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult {
|
private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult {
|
||||||
@@ -427,11 +443,6 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
parsedParams.algorithm == Algorithm.HMAC ||
|
parsedParams.algorithm == Algorithm.HMAC ||
|
||||||
parsedParams.algorithm == Algorithm.TRIPLE_DES
|
parsedParams.algorithm == Algorithm.TRIPLE_DES
|
||||||
|
|
||||||
if (isSymmetric) {
|
|
||||||
SystemLogger.debug("[TX_ID: $txId] Symmetric algorithm ${parsedParams.algorithm} → forwarding to HAL")
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
|
||||||
}
|
|
||||||
|
|
||||||
if (securityLevel == SecurityLevel.STRONGBOX && !isStrongBoxCapable(parsedParams)) {
|
if (securityLevel == SecurityLevel.STRONGBOX && !isStrongBoxCapable(parsedParams)) {
|
||||||
SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}) → forwarding to HAL for rejection")
|
SystemLogger.info("[TX_ID: $txId] StrongBox-unsupported params (algo=${parsedParams.algorithm} size=${parsedParams.keySize}) → forwarding to HAL for rejection")
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
@@ -475,7 +486,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
|
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
|
||||||
TransactionResult.ContinueAndSkipPost
|
InterceptorUtils.createServiceSpecificErrorReply(SECURE_HW_COMMUNICATION_FAILED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,10 +498,55 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
keyId: KeyIdentifier,
|
keyId: KeyIdentifier,
|
||||||
isAttestKeyRequest: Boolean,
|
isAttestKeyRequest: Boolean,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
val startNs = System.nanoTime()
|
val genStartNanos = System.nanoTime()
|
||||||
keyDescriptor.nspace = secureRandom.nextLong()
|
keyDescriptor.nspace = secureRandom.nextLong()
|
||||||
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
|
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
|
||||||
|
|
||||||
|
cleanupKeyData(keyId)
|
||||||
|
|
||||||
|
val isSymmetric = parsedParams.algorithm != Algorithm.EC &&
|
||||||
|
parsedParams.algorithm != Algorithm.RSA
|
||||||
|
|
||||||
|
if (isSymmetric) {
|
||||||
|
val algoName = when (parsedParams.algorithm) {
|
||||||
|
Algorithm.AES -> "AES"
|
||||||
|
Algorithm.HMAC -> "HmacSHA256"
|
||||||
|
else -> throw android.os.ServiceSpecificException(
|
||||||
|
SECURE_HW_COMMUNICATION_FAILED,
|
||||||
|
"Unsupported symmetric algorithm: ${parsedParams.algorithm}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val keyGen = javax.crypto.KeyGenerator.getInstance(algoName)
|
||||||
|
keyGen.init(parsedParams.keySize)
|
||||||
|
val secretKey = keyGen.generateKey()
|
||||||
|
|
||||||
|
val metadata = KeyMetadata().apply {
|
||||||
|
keySecurityLevel = securityLevel
|
||||||
|
key = KeyDescriptor().apply {
|
||||||
|
domain = Domain.KEY_ID
|
||||||
|
nspace = keyDescriptor.nspace
|
||||||
|
alias = null
|
||||||
|
blob = null
|
||||||
|
}
|
||||||
|
certificate = null
|
||||||
|
certificateChain = null
|
||||||
|
authorizations = parsedParams.toAuthorizations(callingUid, securityLevel)
|
||||||
|
modificationTimeMs = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
val response = KeyEntryResponse().apply {
|
||||||
|
this.metadata = metadata
|
||||||
|
iSecurityLevel = original
|
||||||
|
}
|
||||||
|
generatedKeys[keyId] = GeneratedKeyInfo(null, secretKey, keyDescriptor.nspace, response, parsedParams)
|
||||||
|
|
||||||
|
val elapsedMs = (System.nanoTime() - genStartNanos) / 1_000_000
|
||||||
|
val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS
|
||||||
|
val delayMs = floor - elapsedMs
|
||||||
|
if (delayMs > 0) Thread.sleep(delayMs)
|
||||||
|
|
||||||
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
|
val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
|
||||||
generateAttestedKeyPairNative(callingUid, parsedParams)
|
generateAttestedKeyPairNative(callingUid, parsedParams)
|
||||||
?: CertificateGenerator.generateAttestedKeyPair(
|
?: CertificateGenerator.generateAttestedKeyPair(
|
||||||
@@ -502,9 +558,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
} ?: throw Exception("Both native and BouncyCastle cert gen failed.")
|
} ?: throw Exception("Both native and BouncyCastle cert gen failed.")
|
||||||
|
|
||||||
cleanupKeyData(keyId)
|
|
||||||
val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response, parsedParams)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams)
|
||||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||||
|
|
||||||
GeneratedKeyPersistence.save(
|
GeneratedKeyPersistence.save(
|
||||||
@@ -521,7 +576,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
isAttestationKey = isAttestKeyRequest,
|
isAttestationKey = isAttestKeyRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
|
val elapsedMs = (System.nanoTime() - genStartNanos) / 1_000_000
|
||||||
val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS
|
val floor = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_KEYGEN_LATENCY_FLOOR_MS else TEE_LATENCY_FLOOR_MS
|
||||||
val delayMs = floor - elapsedMs
|
val delayMs = floor - elapsedMs
|
||||||
if (delayMs > 0) Thread.sleep(delayMs)
|
if (delayMs > 0) Thread.sleep(delayMs)
|
||||||
@@ -711,7 +766,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
|
val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response, attestation)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, null, record.nspace, response, attestation)
|
||||||
if (record.isAttestationKey) attestationKeys.add(keyId)
|
if (record.isAttestationKey) attestationKeys.add(keyId)
|
||||||
|
|
||||||
SystemLogger.debug("Restored persisted key: $keyId")
|
SystemLogger.debug("Restored persisted key: $keyId")
|
||||||
@@ -739,6 +794,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
private const val STRONGBOX_OP_LATENCY_FLOOR_MS = 80L
|
private const val STRONGBOX_OP_LATENCY_FLOOR_MS = 80L
|
||||||
private const val KEYMINT_TOO_MANY_OPERATIONS = -29
|
private const val KEYMINT_TOO_MANY_OPERATIONS = -29
|
||||||
private const val KEYMINT_CANNOT_ATTEST_IDS = -66
|
private const val KEYMINT_CANNOT_ATTEST_IDS = -66
|
||||||
|
private const val KEYMINT_UNKNOWN_ERROR = -1000
|
||||||
|
private const val SECURE_HW_COMMUNICATION_FAILED = -49
|
||||||
private const val MAX_CONCURRENT_OPS_PER_UID = 15
|
private const val MAX_CONCURRENT_OPS_PER_UID = 15
|
||||||
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
|
private const val STRONGBOX_MAX_CONCURRENT_OPS = 4
|
||||||
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s
|
private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L // 10s
|
||||||
@@ -900,6 +957,34 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
if (this.rsaPublicExponent != null) {
|
if (this.rsaPublicExponent != null) {
|
||||||
authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
|
authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong())))
|
||||||
}
|
}
|
||||||
|
if (this.callerNonce == true) {
|
||||||
|
authList.add(createAuth(Tag.CALLER_NONCE, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
if (this.minMacLength != null) {
|
||||||
|
authList.add(createAuth(Tag.MIN_MAC_LENGTH, KeyParameterValue.integer(this.minMacLength)))
|
||||||
|
}
|
||||||
|
if (this.rollbackResistance == true) {
|
||||||
|
authList.add(createAuth(Tag.ROLLBACK_RESISTANCE, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
if (this.earlyBootOnly == true) {
|
||||||
|
authList.add(createAuth(Tag.EARLY_BOOT_ONLY, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
if (this.allowWhileOnBody == true) {
|
||||||
|
authList.add(createAuth(Tag.ALLOW_WHILE_ON_BODY, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
if (this.trustedUserPresenceRequired == true) {
|
||||||
|
authList.add(createAuth(Tag.TRUSTED_USER_PRESENCE_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
if (this.trustedConfirmationRequired == true) {
|
||||||
|
authList.add(createAuth(Tag.TRUSTED_CONFIRMATION_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
if (this.maxUsesPerBoot != null) {
|
||||||
|
authList.add(createAuth(Tag.MAX_USES_PER_BOOT, KeyParameterValue.integer(this.maxUsesPerBoot)))
|
||||||
|
}
|
||||||
|
if (this.maxBootLevel != null) {
|
||||||
|
authList.add(createAuth(Tag.MAX_BOOT_LEVEL, KeyParameterValue.integer(this.maxBootLevel)))
|
||||||
|
}
|
||||||
|
|
||||||
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.ORIGIN, KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED)))
|
||||||
authList.add(createAuth(Tag.OS_VERSION, KeyParameterValue.integer(AndroidDeviceUtils.osVersion)))
|
authList.add(createAuth(Tag.OS_VERSION, KeyParameterValue.integer(AndroidDeviceUtils.osVersion)))
|
||||||
@@ -916,17 +1001,37 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
|
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) {
|
||||||
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
|
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
|
||||||
}
|
}
|
||||||
authList.add(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
|
|
||||||
authList.add(
|
fun createSwAuth(tag: Int, value: KeyParameterValue): Authorization {
|
||||||
Authorization().apply {
|
val param = KeyParameter().apply {
|
||||||
this.keyParameter =
|
this.tag = tag
|
||||||
KeyParameter().apply {
|
this.value = value
|
||||||
this.tag = Tag.USER_ID
|
}
|
||||||
this.value = KeyParameterValue.integer(callingUid / 100000)
|
return Authorization().apply {
|
||||||
}
|
this.keyParameter = param
|
||||||
this.securityLevel = SecurityLevel.SOFTWARE
|
this.securityLevel = SecurityLevel.SOFTWARE
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
authList.add(createSwAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
|
||||||
|
|
||||||
|
this.activeDateTime?.let {
|
||||||
|
authList.add(createSwAuth(Tag.ACTIVE_DATETIME, KeyParameterValue.dateTime(it.time)))
|
||||||
|
}
|
||||||
|
this.originationExpireDateTime?.let {
|
||||||
|
authList.add(createSwAuth(Tag.ORIGINATION_EXPIRE_DATETIME, KeyParameterValue.dateTime(it.time)))
|
||||||
|
}
|
||||||
|
this.usageExpireDateTime?.let {
|
||||||
|
authList.add(createSwAuth(Tag.USAGE_EXPIRE_DATETIME, KeyParameterValue.dateTime(it.time)))
|
||||||
|
}
|
||||||
|
this.usageCountLimit?.let {
|
||||||
|
authList.add(createSwAuth(Tag.USAGE_COUNT_LIMIT, KeyParameterValue.integer(it)))
|
||||||
|
}
|
||||||
|
if (this.unlockedDeviceRequired == true) {
|
||||||
|
authList.add(createSwAuth(Tag.UNLOCKED_DEVICE_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
authList.add(createSwAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
|
||||||
|
|
||||||
return authList.toTypedArray()
|
return authList.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-12
@@ -3,10 +3,14 @@ package org.matrix.TEESimulator.interception.keystore.shim
|
|||||||
import android.hardware.security.keymint.Algorithm
|
import android.hardware.security.keymint.Algorithm
|
||||||
import android.hardware.security.keymint.BlockMode
|
import android.hardware.security.keymint.BlockMode
|
||||||
import android.hardware.security.keymint.Digest
|
import android.hardware.security.keymint.Digest
|
||||||
|
import android.hardware.security.keymint.KeyParameter
|
||||||
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
import android.hardware.security.keymint.KeyPurpose
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
import android.hardware.security.keymint.PaddingMode
|
import android.hardware.security.keymint.PaddingMode
|
||||||
|
import android.hardware.security.keymint.Tag
|
||||||
import android.os.ServiceSpecificException
|
import android.os.ServiceSpecificException
|
||||||
import android.system.keystore2.IKeystoreOperation
|
import android.system.keystore2.IKeystoreOperation
|
||||||
|
import android.system.keystore2.KeyParameters
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
@@ -22,7 +26,7 @@ private sealed interface CryptoPrimitive {
|
|||||||
fun update(data: ByteArray?): ByteArray?
|
fun update(data: ByteArray?): ByteArray?
|
||||||
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
|
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
|
||||||
fun abort()
|
fun abort()
|
||||||
fun getIv(): ByteArray? = null
|
fun getBeginParameters(): Array<KeyParameter>? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private object JcaAlgorithmMapper {
|
private object JcaAlgorithmMapper {
|
||||||
@@ -125,15 +129,14 @@ private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPri
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class CipherPrimitive(
|
private class CipherPrimitive(
|
||||||
keyPair: KeyPair,
|
cryptoKey: java.security.Key,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
private val opMode: Int,
|
private val opMode: Int,
|
||||||
) : CryptoPrimitive {
|
) : CryptoPrimitive {
|
||||||
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
|
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
|
||||||
private val cipher: Cipher =
|
private val cipher: Cipher =
|
||||||
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
||||||
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
|
init(opMode, cryptoKey)
|
||||||
init(opMode, key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateAad(aadInput: ByteArray?) {
|
override fun updateAad(aadInput: ByteArray?) {
|
||||||
@@ -147,14 +150,45 @@ private class CipherPrimitive(
|
|||||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
|
||||||
if (data != null) cipher.doFinal(data) else cipher.doFinal()
|
if (data != null) cipher.doFinal(data) else cipher.doFinal()
|
||||||
|
|
||||||
override fun getIv(): ByteArray? = if (isAead) cipher.iv else null
|
override fun getBeginParameters(): Array<KeyParameter>? {
|
||||||
|
val iv = cipher.iv ?: return null
|
||||||
|
return arrayOf(
|
||||||
|
KeyParameter().apply {
|
||||||
|
tag = Tag.NONCE
|
||||||
|
value = KeyParameterValue.blob(iv)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun abort() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
|
||||||
|
private val agreement: javax.crypto.KeyAgreement =
|
||||||
|
javax.crypto.KeyAgreement.getInstance("ECDH").apply { init(keyPair.private) }
|
||||||
|
|
||||||
|
override fun update(data: ByteArray?): ByteArray? = null
|
||||||
|
|
||||||
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
|
if (data == null)
|
||||||
|
throw ServiceSpecificException(
|
||||||
|
KeystoreErrorCodes.invalidArgument,
|
||||||
|
"Peer public key required for key agreement",
|
||||||
|
)
|
||||||
|
val peerKey =
|
||||||
|
java.security.KeyFactory.getInstance("EC")
|
||||||
|
.generatePublic(java.security.spec.X509EncodedKeySpec(data))
|
||||||
|
agreement.doPhase(peerKey, true)
|
||||||
|
return agreement.generateSecret()
|
||||||
|
}
|
||||||
|
|
||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SoftwareOperation(
|
class SoftwareOperation(
|
||||||
private val txId: Long,
|
private val txId: Long,
|
||||||
keyPair: KeyPair,
|
keyPair: KeyPair?,
|
||||||
|
secretKey: javax.crypto.SecretKey?,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
private val latencyFloorMs: Long = 0L,
|
private val latencyFloorMs: Long = 0L,
|
||||||
) {
|
) {
|
||||||
@@ -164,8 +198,12 @@ class SoftwareOperation(
|
|||||||
|
|
||||||
var onFinishCallback: (() -> Unit)? = null
|
var onFinishCallback: (() -> Unit)? = null
|
||||||
|
|
||||||
val iv: ByteArray?
|
val beginParameters: KeyParameters?
|
||||||
get() = primitive.getIv()
|
get() {
|
||||||
|
val params = primitive.getBeginParameters() ?: return null
|
||||||
|
if (params.isEmpty()) return null
|
||||||
|
return KeyParameters().apply { keyParameter = params }
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val purpose = params.purpose.firstOrNull()
|
val purpose = params.purpose.firstOrNull()
|
||||||
@@ -174,10 +212,17 @@ class SoftwareOperation(
|
|||||||
|
|
||||||
primitive =
|
primitive =
|
||||||
when (purpose) {
|
when (purpose) {
|
||||||
KeyPurpose.SIGN -> Signer(keyPair, params)
|
KeyPurpose.SIGN -> Signer(keyPair!!, params)
|
||||||
KeyPurpose.VERIFY -> Verifier(keyPair, params)
|
KeyPurpose.VERIFY -> Verifier(keyPair!!, params)
|
||||||
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
KeyPurpose.ENCRYPT -> {
|
||||||
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
val key: java.security.Key = secretKey ?: keyPair!!.public
|
||||||
|
CipherPrimitive(key, params, Cipher.ENCRYPT_MODE)
|
||||||
|
}
|
||||||
|
KeyPurpose.DECRYPT -> {
|
||||||
|
val key: java.security.Key = secretKey ?: keyPair!!.private
|
||||||
|
CipherPrimitive(key, params, Cipher.DECRYPT_MODE)
|
||||||
|
}
|
||||||
|
KeyPurpose.AGREE_KEY -> KeyAgreementPrimitive(keyPair!!)
|
||||||
else ->
|
else ->
|
||||||
throw ServiceSpecificException(
|
throw ServiceSpecificException(
|
||||||
KeystoreErrorCodes.unsupportedPurpose,
|
KeystoreErrorCodes.unsupportedPurpose,
|
||||||
|
|||||||
Reference in New Issue
Block a user