fix(operation): restore CTR mode, AEAD guard, error code resolution, and latency floor

Restores pre-PR157 custom fixes to SoftwareOperation:
- CTR block mode in cipher algorithm mapping
- isAead guard on CipherPrimitive.updateAad (non-GCM throws INVALID_TAG)
- Reflection-based error code resolution for cross-HAL compatibility
- Granular exception mapping (SignatureException, BadPaddingException, etc.)
- latencyFloorMs constructor parameter with LockSupport.parkNanos in finish()
This commit is contained in:
Enginex0
2026-03-22 00:08:41 +01:00
parent b2bf0ce599
commit 3d7fd427a6
@@ -15,36 +15,38 @@ 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
import java.util.concurrent.locks.LockSupport
import javax.crypto.BadPaddingException
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import org.matrix.TEESimulator.attestation.KeyMintAttestation 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
/** Keystore2 error codes for ServiceSpecificException. Negative = KeyMint, positive = Keystore. */
internal object KeystoreErrorCode { internal object KeystoreErrorCode {
const val INVALID_OPERATION_HANDLE = -28 val INVALID_OPERATION_HANDLE: Int by lazy { resolve("ErrorCode", "INVALID_OPERATION_HANDLE", -28) }
const val VERIFICATION_FAILED = -30 val VERIFICATION_FAILED: Int by lazy { resolve("ErrorCode", "VERIFICATION_FAILED", -30) }
const val UNSUPPORTED_PURPOSE = -2 val UNSUPPORTED_PURPOSE: Int by lazy { resolve("ErrorCode", "UNSUPPORTED_PURPOSE", -2) }
const val INCOMPATIBLE_PURPOSE = -3 val INCOMPATIBLE_PURPOSE: Int by lazy { resolve("ErrorCode", "INCOMPATIBLE_PURPOSE", -3) }
const val SYSTEM_ERROR = 4 val INVALID_ARGUMENT: Int by lazy { resolve("ErrorCode", "INVALID_ARGUMENT", -38) }
const val TOO_MUCH_DATA = 21 val INVALID_TAG: Int by lazy { resolve("ErrorCode", "INVALID_TAG", -40) }
const val KEY_EXPIRED = -25 val INVALID_INPUT_LENGTH: Int by lazy { resolve("ErrorCode", "INVALID_INPUT_LENGTH", -21) }
const val KEY_NOT_YET_VALID = -24 val INCOMPATIBLE_KEY: Int by lazy { resolve("ErrorCode", "INCOMPATIBLE_KEY", -31) }
val INCOMPATIBLE_ALGORITHM: Int by lazy { resolve("ErrorCode", "INCOMPATIBLE_ALGORITHM", -18) }
val KEY_EXPIRED: Int by lazy { resolve("ErrorCode", "KEY_EXPIRED", -25) }
val KEY_NOT_YET_VALID: Int by lazy { resolve("ErrorCode", "KEY_NOT_YET_VALID", -24) }
val CALLER_NONCE_PROHIBITED: Int by lazy { resolve("ErrorCode", "CALLER_NONCE_PROHIBITED", -55) }
val UNKNOWN_ERROR: Int by lazy { resolve("ErrorCode", "UNKNOWN_ERROR", -1000) }
val SYSTEM_ERROR: Int by lazy { resolve("ResponseCode", "SYSTEM_ERROR", 4, keystore = true) }
val TOO_MUCH_DATA: Int by lazy { resolve("ResponseCode", "TOO_MUCH_DATA", 21, keystore = true) }
val PERMISSION_DENIED: Int by lazy { resolve("ResponseCode", "PERMISSION_DENIED", 6, keystore = true) }
val KEY_NOT_FOUND: Int by lazy { resolve("ResponseCode", "KEY_NOT_FOUND", 7, keystore = true) }
/** KeyMint ErrorCode::CALLER_NONCE_PROHIBITED */ private fun resolve(enumName: String, field: String, fallback: Int, keystore: Boolean = false): Int {
const val CALLER_NONCE_PROHIBITED = -55 val pkg = if (keystore) "android.system.keystore2" else "android.hardware.security.keymint"
return runCatching { Class.forName("$pkg.$enumName").getField(field).getInt(null) }
/** KeyMint ErrorCode::INVALID_ARGUMENT */ .getOrDefault(fallback)
const val INVALID_ARGUMENT = -38 }
/** KeyMint ErrorCode::INVALID_TAG */
const val INVALID_TAG = -40
/** Keystore2 ResponseCode::PERMISSION_DENIED */
const val PERMISSION_DENIED = 6
/** Keystore2 ResponseCode::KEY_NOT_FOUND */
const val KEY_NOT_FOUND = 7
} }
// A sealed interface to represent the different cryptographic operations we can perform. // A sealed interface to represent the different cryptographic operations we can perform.
@@ -99,8 +101,9 @@ private object JcaAlgorithmMapper {
when (params.blockMode.firstOrNull()) { when (params.blockMode.firstOrNull()) {
BlockMode.ECB -> "ECB" BlockMode.ECB -> "ECB"
BlockMode.CBC -> "CBC" BlockMode.CBC -> "CBC"
BlockMode.CTR -> "CTR"
BlockMode.GCM -> "GCM" BlockMode.GCM -> "GCM"
else -> "ECB" // Default for RSA else -> "ECB"
} }
val padding = val padding =
when (params.padding.firstOrNull()) { when (params.padding.firstOrNull()) {
@@ -183,8 +186,10 @@ private class CipherPrimitive(
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply { Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
init(opMode, cryptoKey) init(opMode, cryptoKey)
} }
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
override fun updateAad(data: ByteArray?) { override fun updateAad(data: ByteArray?) {
if (!isAead) throw ServiceSpecificException(KeystoreErrorCode.INVALID_TAG)
if (data != null) cipher.updateAAD(data) if (data != null) cipher.updateAAD(data)
} }
@@ -247,6 +252,7 @@ class SoftwareOperation(
keyPair: KeyPair?, keyPair: KeyPair?,
secretKey: javax.crypto.SecretKey?, secretKey: javax.crypto.SecretKey?,
params: KeyMintAttestation, params: KeyMintAttestation,
private val latencyFloorMs: Long = 0L,
var onFinishCallback: (() -> Unit)? = null, var onFinishCallback: (() -> Unit)? = null,
) { ) {
private val primitive: CryptoPrimitive private val primitive: CryptoPrimitive
@@ -319,27 +325,42 @@ class SoftwareOperation(
} catch (e: Exception) { } catch (e: Exception) {
finalized = true finalized = true
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e) SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message) throw mapToServiceSpecificException(e)
} }
} }
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? { fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
checkActive() checkActive()
val startNs = if (latencyFloorMs > 0) System.nanoTime() else 0L
try { try {
val result = primitive.finish(data, signature) val result = primitive.finish(data, signature)
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.") SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
if (latencyFloorMs > 0) {
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
val delayMs = latencyFloorMs - elapsedMs
if (delayMs > 0) LockSupport.parkNanos(delayMs * 1_000_000)
}
onFinishCallback?.invoke() onFinishCallback?.invoke()
return result return result
} catch (e: ServiceSpecificException) { } catch (e: ServiceSpecificException) {
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 ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message) throw mapToServiceSpecificException(e)
} finally { } finally {
finalized = true finalized = true
} }
} }
private fun mapToServiceSpecificException(e: Exception): ServiceSpecificException = when (e) {
is ServiceSpecificException -> e
is SignatureException -> ServiceSpecificException(KeystoreErrorCode.VERIFICATION_FAILED, e.message)
is BadPaddingException -> ServiceSpecificException(KeystoreErrorCode.INVALID_ARGUMENT, e.message)
is IllegalBlockSizeException -> ServiceSpecificException(KeystoreErrorCode.INVALID_INPUT_LENGTH, e.message)
is java.security.InvalidKeyException -> ServiceSpecificException(KeystoreErrorCode.INCOMPATIBLE_KEY, e.message)
else -> ServiceSpecificException(KeystoreErrorCode.UNKNOWN_ERROR, e.message)
}
fun abort() { fun abort() {
checkActive() checkActive()
finalized = true finalized = true