feat(operation): add AOSP-compliant error handling, authorize_create, and GCM IV
SoftwareOperation now throws ServiceSpecificException for all error paths instead of raw Java exceptions, matching AIDL wire format. updateAad on non-AEAD operations returns INVALID_TAG (-76) per AOSP operation.rs. SoftwareOperationBinder methods are @Synchronized to match AOSP Mutex semantics. GCM encrypt operations return the generated IV in CreateOperationResponse.parameters. AuthorizeCreate enforces PURPOSE validation, algorithm-purpose compatibility (EC rejects ENCRYPT/DECRYPT, RSA rejects AGREE_KEY), temporal constraints (ACTIVE_DATETIME, ORIGINATION_EXPIRE, USAGE_EXPIRE), and CALLER_NONCE prohibition. GeneratedKeyInfo carries keyParams for authorize_create enforcement on software createOperation.
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
|
import android.hardware.security.keymint.Algorithm
|
||||||
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
|
import android.hardware.security.keymint.KeyParameter
|
||||||
|
import android.hardware.security.keymint.Tag
|
||||||
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
|
|
||||||
|
object AuthorizeCreate {
|
||||||
|
|
||||||
|
fun check(
|
||||||
|
keyParams: KeyMintAttestation?,
|
||||||
|
opParams: KeyMintAttestation,
|
||||||
|
rawOpParams: Array<KeyParameter>? = null,
|
||||||
|
): Int? {
|
||||||
|
if (keyParams == null) return null
|
||||||
|
return checkPurpose(keyParams, opParams)
|
||||||
|
?: checkAlgorithmPurpose(keyParams, opParams)
|
||||||
|
?: checkTemporalValidity(keyParams, opParams)
|
||||||
|
?: checkCallerNonce(keyParams, rawOpParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPurpose(keyParams: KeyMintAttestation, opParams: KeyMintAttestation): Int? {
|
||||||
|
val requestedPurpose = opParams.purpose.firstOrNull() ?: return null
|
||||||
|
if (requestedPurpose == KeyPurpose.WRAP_KEY)
|
||||||
|
return KeystoreErrorCodes.incompatiblePurpose
|
||||||
|
if (requestedPurpose !in keyParams.purpose)
|
||||||
|
return KeystoreErrorCodes.incompatiblePurpose
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAlgorithmPurpose(keyParams: KeyMintAttestation, opParams: KeyMintAttestation): Int? {
|
||||||
|
val purpose = opParams.purpose.firstOrNull() ?: return null
|
||||||
|
return when (keyParams.algorithm) {
|
||||||
|
Algorithm.EC -> when (purpose) {
|
||||||
|
KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT -> KeystoreErrorCodes.unsupportedPurpose
|
||||||
|
KeyPurpose.AGREE_KEY -> null
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
Algorithm.RSA -> when (purpose) {
|
||||||
|
KeyPurpose.AGREE_KEY -> KeystoreErrorCodes.unsupportedPurpose
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkTemporalValidity(keyParams: KeyMintAttestation, opParams: KeyMintAttestation): Int? {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val purpose = opParams.purpose.firstOrNull()
|
||||||
|
|
||||||
|
keyParams.activeDateTime?.let { activeDate ->
|
||||||
|
if (now < activeDate.time) return KeystoreErrorCodes.keyNotYetValid
|
||||||
|
}
|
||||||
|
|
||||||
|
keyParams.originationExpireDateTime?.let { expireDate ->
|
||||||
|
if (purpose == KeyPurpose.SIGN || purpose == KeyPurpose.ENCRYPT) {
|
||||||
|
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyParams.usageExpireDateTime?.let { expireDate ->
|
||||||
|
if (purpose == KeyPurpose.VERIFY || purpose == KeyPurpose.DECRYPT) {
|
||||||
|
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCallerNonce(keyParams: KeyMintAttestation, rawOpParams: Array<KeyParameter>?): Int? {
|
||||||
|
if (keyParams.callerNonce == true) return null
|
||||||
|
val hasNonce = rawOpParams?.any { it.tag == Tag.NONCE } == true
|
||||||
|
if (hasNonce) return KeystoreErrorCodes.callerNonceProhibited
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
-4
@@ -1,8 +1,10 @@
|
|||||||
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.Algorithm
|
||||||
|
import android.hardware.security.keymint.BlockMode
|
||||||
import android.hardware.security.keymint.EcCurve
|
import android.hardware.security.keymint.EcCurve
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
import android.hardware.security.keymint.KeyParameterValue
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
import android.hardware.security.keymint.KeyOrigin
|
import android.hardware.security.keymint.KeyOrigin
|
||||||
import android.hardware.security.keymint.SecurityLevel
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
@@ -47,6 +49,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val keyPair: KeyPair,
|
val keyPair: KeyPair,
|
||||||
val nspace: Long,
|
val nspace: Long,
|
||||||
val response: KeyEntryResponse,
|
val response: KeyEntryResponse,
|
||||||
|
val keyParams: KeyMintAttestation? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||||
@@ -132,6 +135,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
GeneratedKeyPersistence.delete(keyId)
|
GeneratedKeyPersistence.delete(keyId)
|
||||||
}
|
}
|
||||||
attestationKeys.remove(keyId)
|
attestationKeys.remove(keyId)
|
||||||
|
importedKeys.add(keyId)
|
||||||
} 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)
|
||||||
|
|
||||||
@@ -158,7 +162,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val backdoor = getBackdoor(target)
|
val backdoor = getBackdoor(target)
|
||||||
if (backdoor != null) {
|
if (backdoor != null) {
|
||||||
val interceptor = OperationInterceptor(operation, backdoor)
|
val interceptor = OperationInterceptor(operation, backdoor)
|
||||||
register(backdoor, operationBinder, interceptor)
|
register(backdoor, operationBinder, interceptor, OperationInterceptor.INTERCEPTED_CODES)
|
||||||
interceptedOperations[operationBinder] = interceptor
|
interceptedOperations[operationBinder] = interceptor
|
||||||
} else {
|
} else {
|
||||||
SystemLogger.error(
|
SystemLogger.error(
|
||||||
@@ -281,6 +285,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthorizeCreate.check(generatedKeyInfo.keyParams, parsedParams, params)?.let { errorCode ->
|
||||||
|
SystemLogger.info("[TX_ID: $txId] authorize_create rejected: errorCode=$errorCode")
|
||||||
|
return InterceptorUtils.createErrorReply(errorCode)
|
||||||
|
}
|
||||||
|
|
||||||
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, parsedParams, opLatency)
|
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams, opLatency)
|
||||||
val maxOps = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_MAX_CONCURRENT_OPS else MAX_CONCURRENT_OPS_PER_UID
|
val maxOps = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_MAX_CONCURRENT_OPS else MAX_CONCURRENT_OPS_PER_UID
|
||||||
@@ -291,6 +300,16 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
CreateOperationResponse().apply {
|
CreateOperationResponse().apply {
|
||||||
iOperation = operationBinder
|
iOperation = operationBinder
|
||||||
operationChallenge = null
|
operationChallenge = null
|
||||||
|
softwareOperation.iv?.let { iv ->
|
||||||
|
parameters = KeyParameters().apply {
|
||||||
|
keyParameter = arrayOf(
|
||||||
|
KeyParameter().apply {
|
||||||
|
tag = Tag.NONCE
|
||||||
|
value = KeyParameterValue.blob(iv)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response)
|
return InterceptorUtils.createTypedObjectReply(response)
|
||||||
@@ -423,7 +442,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
cleanupKeyData(keyId)
|
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)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response, parsedParams)
|
||||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||||
|
|
||||||
GeneratedKeyPersistence.save(
|
GeneratedKeyPersistence.save(
|
||||||
@@ -610,10 +629,27 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
manufacturer = null,
|
manufacturer = null,
|
||||||
model = null,
|
model = null,
|
||||||
secondImei = null,
|
secondImei = null,
|
||||||
|
activeDateTime = null,
|
||||||
|
originationExpireDateTime = null,
|
||||||
|
usageExpireDateTime = null,
|
||||||
|
usageCountLimit = null,
|
||||||
|
callerNonce = null,
|
||||||
|
unlockedDeviceRequired = null,
|
||||||
|
includeUniqueId = null,
|
||||||
|
rollbackResistance = null,
|
||||||
|
earlyBootOnly = null,
|
||||||
|
allowWhileOnBody = null,
|
||||||
|
trustedUserPresenceRequired = null,
|
||||||
|
trustedConfirmationRequired = null,
|
||||||
|
noAuthRequired = null,
|
||||||
|
maxUsesPerBoot = null,
|
||||||
|
maxBootLevel = null,
|
||||||
|
minMacLength = null,
|
||||||
|
rsaOaepMgfDigest = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = buildKeyEntryResponse(record.uid, 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, 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")
|
||||||
@@ -688,6 +724,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
"createOperation",
|
"createOperation",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val INTERCEPTED_CODES =
|
||||||
|
intArrayOf(GENERATE_KEY_TRANSACTION, IMPORT_KEY_TRANSACTION, CREATE_OPERATION_TRANSACTION)
|
||||||
|
|
||||||
private val transactionNames: Map<Int, String> by lazy {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
IKeystoreSecurityLevel.Stub::class
|
IKeystoreSecurityLevel.Stub::class
|
||||||
.java
|
.java
|
||||||
@@ -702,6 +741,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||||
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
|
val importedKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
||||||
|
|
||||||
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
||||||
@@ -730,6 +770,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
if (attestationKeys.remove(keyId)) {
|
if (attestationKeys.remove(keyId)) {
|
||||||
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
||||||
}
|
}
|
||||||
|
importedKeys.remove(keyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
|
fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
|
||||||
@@ -754,6 +795,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
generatedKeys.clear()
|
generatedKeys.clear()
|
||||||
patchedChains.clear()
|
patchedChains.clear()
|
||||||
attestationKeys.clear()
|
attestationKeys.clear()
|
||||||
|
importedKeys.clear()
|
||||||
GeneratedKeyPersistence.deleteAll()
|
GeneratedKeyPersistence.deleteAll()
|
||||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||||
}
|
}
|
||||||
@@ -783,6 +825,7 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
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.blockMode.forEach { authList.add(createAuth(Tag.BLOCK_MODE, KeyParameterValue.blockMode(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))) }
|
this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) }
|
||||||
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
||||||
@@ -806,7 +849,16 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
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(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
|
||||||
authList.add(createAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
|
authList.add(
|
||||||
|
Authorization().apply {
|
||||||
|
this.keyParameter =
|
||||||
|
KeyParameter().apply {
|
||||||
|
this.tag = Tag.USER_ID
|
||||||
|
this.value = KeyParameterValue.integer(callingUid / 100000)
|
||||||
|
}
|
||||||
|
this.securityLevel = SecurityLevel.SOFTWARE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return authList.toTypedArray()
|
return authList.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-21
@@ -5,7 +5,6 @@ import android.hardware.security.keymint.BlockMode
|
|||||||
import android.hardware.security.keymint.Digest
|
import android.hardware.security.keymint.Digest
|
||||||
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.os.RemoteException
|
|
||||||
import android.os.ServiceSpecificException
|
import android.os.ServiceSpecificException
|
||||||
import android.system.keystore2.IKeystoreOperation
|
import android.system.keystore2.IKeystoreOperation
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
@@ -16,15 +15,16 @@ import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
|||||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
// A sealed interface to represent the different cryptographic operations we can perform.
|
|
||||||
private sealed interface CryptoPrimitive {
|
private sealed interface CryptoPrimitive {
|
||||||
fun updateAad(aadInput: ByteArray?) {}
|
fun updateAad(aadInput: ByteArray?) {
|
||||||
|
throw ServiceSpecificException(KeystoreErrorCodes.invalidTag)
|
||||||
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper object to map KeyMint constants to JCA algorithm strings.
|
|
||||||
private object JcaAlgorithmMapper {
|
private object JcaAlgorithmMapper {
|
||||||
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
|
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
|
||||||
val digest =
|
val digest =
|
||||||
@@ -41,8 +41,9 @@ private object JcaAlgorithmMapper {
|
|||||||
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
|
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
|
||||||
}
|
}
|
||||||
else ->
|
else ->
|
||||||
throw IllegalArgumentException(
|
throw ServiceSpecificException(
|
||||||
"Unsupported signature algorithm: ${params.algorithm}"
|
KeystoreErrorCodes.incompatibleAlgorithm,
|
||||||
|
"Unsupported signature algorithm: ${params.algorithm}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +54,9 @@ private object JcaAlgorithmMapper {
|
|||||||
Algorithm.RSA -> "RSA"
|
Algorithm.RSA -> "RSA"
|
||||||
Algorithm.AES -> "AES"
|
Algorithm.AES -> "AES"
|
||||||
else ->
|
else ->
|
||||||
throw IllegalArgumentException(
|
throw ServiceSpecificException(
|
||||||
"Unsupported cipher algorithm: ${params.algorithm}"
|
KeystoreErrorCodes.incompatibleAlgorithm,
|
||||||
|
"Unsupported cipher algorithm: ${params.algorithm}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val blockMode =
|
val blockMode =
|
||||||
@@ -78,7 +80,6 @@ private object JcaAlgorithmMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concrete implementation for Signing.
|
|
||||||
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
private val signature: Signature =
|
private val signature: Signature =
|
||||||
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||||
@@ -98,7 +99,6 @@ private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimi
|
|||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concrete implementation for Verification.
|
|
||||||
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
private val signature: Signature =
|
private val signature: Signature =
|
||||||
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||||
@@ -112,36 +112,43 @@ private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPri
|
|||||||
|
|
||||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
if (data != null) update(data)
|
if (data != null) update(data)
|
||||||
if (signature == null) throw SignatureException("Signature to verify is null")
|
if (signature == null) {
|
||||||
|
throw ServiceSpecificException(KeystoreErrorCodes.verificationFailed, "Signature to verify is null")
|
||||||
|
}
|
||||||
if (!this.signature.verify(signature)) {
|
if (!this.signature.verify(signature)) {
|
||||||
// Throwing an exception is how Keystore signals verification failure.
|
throw ServiceSpecificException(KeystoreErrorCodes.verificationFailed, "Signature verification failed")
|
||||||
throw SignatureException("Signature verification failed")
|
|
||||||
}
|
}
|
||||||
// A successful verification returns no data.
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concrete implementation for Encryption/Decryption.
|
|
||||||
private class CipherPrimitive(
|
private class CipherPrimitive(
|
||||||
keyPair: KeyPair,
|
keyPair: KeyPair,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
private val opMode: Int,
|
private val opMode: Int,
|
||||||
) : CryptoPrimitive {
|
) : CryptoPrimitive {
|
||||||
|
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
|
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
|
||||||
init(opMode, key)
|
init(opMode, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAad(aadInput: ByteArray?) {
|
||||||
|
if (!isAead) throw ServiceSpecificException(KeystoreErrorCodes.invalidTag)
|
||||||
|
if (aadInput != null) cipher.updateAAD(aadInput)
|
||||||
|
}
|
||||||
|
|
||||||
override fun update(data: ByteArray?): ByteArray? =
|
override fun update(data: ByteArray?): ByteArray? =
|
||||||
if (data != null) cipher.update(data) else null
|
if (data != null) cipher.update(data) else null
|
||||||
|
|
||||||
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 abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +162,9 @@ class SoftwareOperation(
|
|||||||
@Volatile var finalized = false
|
@Volatile var finalized = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
val iv: ByteArray?
|
||||||
|
get() = primitive.getIv()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val purpose = params.purpose.firstOrNull()
|
val purpose = params.purpose.firstOrNull()
|
||||||
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
||||||
@@ -167,7 +177,10 @@ class SoftwareOperation(
|
|||||||
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
||||||
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
||||||
else ->
|
else ->
|
||||||
throw UnsupportedOperationException("Unsupported operation purpose: $purpose")
|
throw ServiceSpecificException(
|
||||||
|
KeystoreErrorCodes.unsupportedPurpose,
|
||||||
|
"Unsupported operation purpose: $purpose",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +215,7 @@ class SoftwareOperation(
|
|||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
||||||
throw e
|
throw mapToServiceSpecificException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +237,7 @@ class SoftwareOperation(
|
|||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
||||||
throw e
|
throw mapToServiceSpecificException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,13 +247,20 @@ class SoftwareOperation(
|
|||||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mapToServiceSpecificException(e: Exception): ServiceSpecificException = when (e) {
|
||||||
|
is SignatureException -> ServiceSpecificException(KeystoreErrorCodes.verificationFailed, e.message)
|
||||||
|
is javax.crypto.BadPaddingException -> ServiceSpecificException(KeystoreErrorCodes.invalidArgument, e.message)
|
||||||
|
is javax.crypto.IllegalBlockSizeException -> ServiceSpecificException(KeystoreErrorCodes.invalidInputLength, e.message)
|
||||||
|
is java.security.InvalidKeyException -> ServiceSpecificException(KeystoreErrorCodes.incompatibleKey, e.message)
|
||||||
|
else -> ServiceSpecificException(KeystoreErrorCodes.unknownError, e.message)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// AOSP keystore2 operation.rs: const MAX_RECEIVE_DATA: usize = 0x8000
|
|
||||||
private const val MAX_RECEIVE_DATA = 0x8000
|
private const val MAX_RECEIVE_DATA = 0x8000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private object KeystoreErrorCodes {
|
internal object KeystoreErrorCodes {
|
||||||
val tooMuchData: Int by lazy {
|
val tooMuchData: Int by lazy {
|
||||||
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
|
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
|
||||||
}
|
}
|
||||||
@@ -249,7 +269,55 @@ private object KeystoreErrorCodes {
|
|||||||
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28)
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveField(className: String, fieldName: String, fallback: Int): Int =
|
val invalidTag: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_TAG", -76)
|
||||||
|
}
|
||||||
|
|
||||||
|
val verificationFailed: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "VERIFICATION_FAILED", -30)
|
||||||
|
}
|
||||||
|
|
||||||
|
val invalidArgument: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_ARGUMENT", -38)
|
||||||
|
}
|
||||||
|
|
||||||
|
val invalidInputLength: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_INPUT_LENGTH", -21)
|
||||||
|
}
|
||||||
|
|
||||||
|
val incompatibleKey: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_KEY", -31)
|
||||||
|
}
|
||||||
|
|
||||||
|
val incompatiblePurpose: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_PURPOSE", -13)
|
||||||
|
}
|
||||||
|
|
||||||
|
val unsupportedPurpose: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "UNSUPPORTED_PURPOSE", -14)
|
||||||
|
}
|
||||||
|
|
||||||
|
val incompatibleAlgorithm: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_ALGORITHM", -18)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyNotYetValid: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_NOT_YET_VALID", -39)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyExpired: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_EXPIRED", -40)
|
||||||
|
}
|
||||||
|
|
||||||
|
val callerNonceProhibited: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "CALLER_NONCE_PROHIBITED", -55)
|
||||||
|
}
|
||||||
|
|
||||||
|
val unknownError: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "UNKNOWN_ERROR", -1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveField(className: String, fieldName: String, fallback: Int): Int =
|
||||||
runCatching {
|
runCatching {
|
||||||
Class.forName(className).getField(fieldName).getInt(null)
|
Class.forName(className).getField(fieldName).getInt(null)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
@@ -261,18 +329,22 @@ private object KeystoreErrorCodes {
|
|||||||
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
||||||
IKeystoreOperation.Stub() {
|
IKeystoreOperation.Stub() {
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun updateAad(aadInput: ByteArray?) {
|
override fun updateAad(aadInput: ByteArray?) {
|
||||||
operation.updateAad(aadInput)
|
operation.updateAad(aadInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun update(input: ByteArray?): ByteArray? {
|
override fun update(input: ByteArray?): ByteArray? {
|
||||||
return operation.update(input)
|
return operation.update(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
|
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
return operation.finish(input, signature)
|
return operation.finish(input, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun abort() {
|
override fun abort() {
|
||||||
operation.abort()
|
operation.abort()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user