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.
This commit is contained in:
+8
-1
@@ -218,7 +218,14 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.")
|
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.")
|
||||||
|
|
||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
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 softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams)
|
||||||
val operationBinder = SoftwareOperationBinder(softwareOperation)
|
val operationBinder = SoftwareOperationBinder(softwareOperation)
|
||||||
|
|||||||
+55
-14
@@ -6,6 +6,7 @@ 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.RemoteException
|
||||||
|
import android.os.ServiceSpecificException
|
||||||
import android.system.keystore2.IKeystoreOperation
|
import android.system.keystore2.IKeystoreOperation
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.Signature
|
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.
|
// 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 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,17 +142,11 @@ private class CipherPrimitive(
|
|||||||
override fun abort() {}
|
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) {
|
class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) {
|
||||||
// This now holds the specific strategy object (Signer, Verifier, etc.)
|
|
||||||
private val primitive: CryptoPrimitive
|
private val primitive: CryptoPrimitive
|
||||||
|
@Volatile private var finalized = false
|
||||||
|
|
||||||
init {
|
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 purpose = params.purpose.firstOrNull()
|
||||||
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
||||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.")
|
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? {
|
fun update(data: ByteArray?): ByteArray? {
|
||||||
|
checkActive()
|
||||||
|
checkInputLength(data)
|
||||||
try {
|
try {
|
||||||
return primitive.update(data)
|
return primitive.update(data)
|
||||||
|
} catch (e: ServiceSpecificException) {
|
||||||
|
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 e
|
||||||
@@ -178,38 +191,66 @@ class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMin
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
|
checkActive()
|
||||||
|
checkInputLength(data)
|
||||||
try {
|
try {
|
||||||
val result = primitive.finish(data, signature)
|
val result = primitive.finish(data, signature)
|
||||||
|
finalized = true
|
||||||
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
||||||
return result
|
return result
|
||||||
|
} catch (e: ServiceSpecificException) {
|
||||||
|
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)
|
||||||
// Re-throw the exception so the binder can report it to the client.
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun abort() {
|
fun abort() {
|
||||||
|
finalized = true
|
||||||
primitive.abort()
|
primitive.abort()
|
||||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
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) :
|
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
||||||
IKeystoreOperation.Stub() {
|
IKeystoreOperation.Stub() {
|
||||||
|
|
||||||
@Throws(RemoteException::class)
|
override fun updateAad(aadInput: ByteArray?) {
|
||||||
|
operation.updateAad(aadInput)
|
||||||
|
}
|
||||||
|
|
||||||
override fun update(input: ByteArray?): ByteArray? {
|
override fun update(input: ByteArray?): ByteArray? {
|
||||||
return operation.update(input)
|
return operation.update(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(RemoteException::class)
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(RemoteException::class)
|
|
||||||
override fun abort() {
|
override fun abort() {
|
||||||
operation.abort()
|
operation.abort()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user