From 6fc3269229bc023fdb5ded917185b1b80a361fb5 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Tue, 17 Mar 2026 07:04:59 +0100 Subject: [PATCH] fix(operation): match AOSP error-path semantics for software operations KeyDetector's OperationErrorPathChecker (flag 0x400000) probes three error-path behaviors that real keystore2 operations expose. Our SoftwareOperationBinder was missing all three, plus had no updateAad implementation which caused AbstractMethodError on Android 16 where the runtime Stub declares it abstract. SoftwareOperation changes: - Add finalized state tracking; post-abort calls now throw INVALID_OPERATION_HANDLE (-28) matching AOSP operation.rs - Add input length guard (0x8000) throwing TOO_MUCH_DATA (29) matching AOSP operation.rs MAX_RECEIVE_DATA - Add updateAad to CryptoPrimitive interface and SoftwareOperationBinder - Add KeystoreErrorCodes with runtime reflection + AOSP fallback values KeyMintSecurityLevelInterceptor changes: - Infer algorithm from stored key pair when operation params omit ALGORITHM tag, matching AOSP behavior where createOperation uses the key's stored algorithm rather than requiring it in op params Stub addition: - ServiceSpecificException compile stub (framework-internal class resolved at runtime on device) Tested on OnePlus Android 16 (SDK 36), KeyDetector passes all three probes: updateAad succeeds, TOO_MUCH_DATA returns code=21, INVALID_OPERATION_HANDLE returns after abort. --- .../shim/KeyMintSecurityLevelInterceptor.kt | 9 ++- .../keystore/shim/SoftwareOperation.kt | 69 +++++++++++++++---- .../android/os/ServiceSpecificException.java | 14 ++++ 3 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 stub/src/main/java/android/os/ServiceSpecificException.java diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 955298a..010bc2d 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -218,7 +218,14 @@ class KeyMintSecurityLevelInterceptor( SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.") val params = data.createTypedArray(KeyParameter.CREATOR)!! - val parsedParams = KeyMintAttestation(params) + val parsedParams = KeyMintAttestation(params).let { p -> + if (p.algorithm != 0) p + else p.copy(algorithm = when (generatedKeyInfo.keyPair.private.algorithm) { + "EC" -> Algorithm.EC + "RSA" -> Algorithm.RSA + else -> p.algorithm + }) + } val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams) val operationBinder = SoftwareOperationBinder(softwareOperation) diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt index 79cd2c9..9eb0dfb 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt @@ -6,6 +6,7 @@ import android.hardware.security.keymint.Digest import android.hardware.security.keymint.KeyPurpose import android.hardware.security.keymint.PaddingMode import android.os.RemoteException +import android.os.ServiceSpecificException import android.system.keystore2.IKeystoreOperation import java.security.KeyPair import java.security.Signature @@ -17,10 +18,9 @@ import org.matrix.TEESimulator.logging.SystemLogger // A sealed interface to represent the different cryptographic operations we can perform. private sealed interface CryptoPrimitive { + fun updateAad(aadInput: ByteArray?) {} fun update(data: ByteArray?): ByteArray? - fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? - fun abort() } @@ -142,17 +142,11 @@ private class CipherPrimitive( override fun abort() {} } -/** - * A software-only implementation of a cryptographic operation. This class acts as a controller, - * delegating to a specific cryptographic primitive based on the operation's purpose. - */ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) { - // This now holds the specific strategy object (Signer, Verifier, etc.) private val primitive: CryptoPrimitive + @Volatile private var finalized = false init { - // The "Strategy" pattern: choose the implementation based on the purpose. - // For simplicity, we only consider the first purpose listed. val purpose = params.purpose.firstOrNull() val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN" SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.") @@ -168,9 +162,28 @@ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMin } } + private fun checkActive() { + if (finalized) throw ServiceSpecificException(KeystoreErrorCodes.invalidOperationHandle) + } + + private fun checkInputLength(data: ByteArray?) { + if (data != null && data.size > MAX_RECEIVE_DATA) + throw ServiceSpecificException(KeystoreErrorCodes.tooMuchData) + } + + fun updateAad(aadInput: ByteArray?) { + checkActive() + checkInputLength(aadInput) + primitive.updateAad(aadInput) + } + fun update(data: ByteArray?): ByteArray? { + checkActive() + checkInputLength(data) try { return primitive.update(data) + } catch (e: ServiceSpecificException) { + throw e } catch (e: Exception) { SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e) throw e @@ -178,38 +191,66 @@ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMin } fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? { + checkActive() + checkInputLength(data) try { val result = primitive.finish(data, signature) + finalized = true SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.") return result + } catch (e: ServiceSpecificException) { + throw e } catch (e: Exception) { SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e) - // Re-throw the exception so the binder can report it to the client. throw e } } fun abort() { + finalized = true primitive.abort() SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.") } + + companion object { + // AOSP keystore2 operation.rs: const MAX_RECEIVE_DATA: usize = 0x8000 + private const val MAX_RECEIVE_DATA = 0x8000 + } +} + +private object KeystoreErrorCodes { + val tooMuchData: Int by lazy { + resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 29) + } + + val invalidOperationHandle: Int by lazy { + resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28) + } + + private fun resolveField(className: String, fieldName: String, fallback: Int): Int = + runCatching { + Class.forName(className).getField(fieldName).getInt(null) + }.getOrElse { + SystemLogger.debug("Resolved $className.$fieldName via fallback: $fallback") + fallback + } } -/** The Binder interface for our [SoftwareOperation]. */ class SoftwareOperationBinder(private val operation: SoftwareOperation) : IKeystoreOperation.Stub() { - @Throws(RemoteException::class) + override fun updateAad(aadInput: ByteArray?) { + operation.updateAad(aadInput) + } + override fun update(input: ByteArray?): ByteArray? { return operation.update(input) } - @Throws(RemoteException::class) override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? { return operation.finish(input, signature) } - @Throws(RemoteException::class) override fun abort() { operation.abort() } diff --git a/stub/src/main/java/android/os/ServiceSpecificException.java b/stub/src/main/java/android/os/ServiceSpecificException.java new file mode 100644 index 0000000..6082d49 --- /dev/null +++ b/stub/src/main/java/android/os/ServiceSpecificException.java @@ -0,0 +1,14 @@ +package android.os; + +public class ServiceSpecificException extends RuntimeException { + public final int errorCode; + + public ServiceSpecificException(int errorCode) { + this.errorCode = errorCode; + } + + public ServiceSpecificException(int errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +}