Skip to content

Commit b5c0ae2

Browse files
committed
Enforce 128-byte limit for attestation challenge
This commit aligns the simulator's behavior with the Android Keymaster/KeyMint specification by enforcing a maximum length of 128 bytes for the attestation challenge. Previously, the simulator accepted attestation challenges of arbitrary length during `generateKey`. This discrepancy allowed detection tools to identify the emulated environment by intentionally sending an oversized challenge (e.g., > 128 bytes) and observing that it was accepted instead of rejected. The implementation now validates the size of the `TAG_ATTESTATION_CHALLENGE` parameter. If the challenge exceeds the limit, the transaction is intercepted, and a `ServiceSpecificException` is constructed manually via Binder (using the `EX_SERVICE_SPECIFIC` header) to return the `INVALID_INPUT_LENGTH` (-21) error code. This matches the error code and Binder-visible behavior defined by the KeyMint specification. See AOSP source for the length constraint and error definition: https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/android_keymaster/android_keymaster.cpp;l=330 https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp;l=257 https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/ErrorCode.aidl;l=48
1 parent dba7af3 commit b5c0ae2

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

app/src/main/java/org/matrix/TEESimulator/interception/keystore/InterceptorUtils.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,39 @@ object InterceptorUtils {
9696
fun hasException(reply: Parcel): Boolean {
9797
return runCatching { reply.readException() }.exceptionOrNull() != null
9898
}
99+
100+
/**
101+
* Creates an `OverrideReply` containing a ServiceSpecificException encoded in a Parcel.
102+
*
103+
* This method is used to return KeyMint/Keymaster error codes directly through Binder, such as
104+
* `ErrorCode.INVALID_INPUT_LENGTH` (-21), so that the caller observes the same exception
105+
* behavior as with a real KeyMint HAL implementation.
106+
*
107+
* @param errorCode KeyMint error code to return (for example, -21 for INVALID_INPUT_LENGTH).
108+
* @param message Optional error message associated with the error code.
109+
*/
110+
fun createErrorReply(
111+
errorCode: Int,
112+
message: String?,
113+
): BinderInterceptor.TransactionResult.OverrideReply {
114+
val parcel = Parcel.obtain()
115+
try {
116+
// Write a ServiceSpecificException into the Parcel.
117+
// Parcel.EX_SERVICE_SPECIFIC is defined as -8.
118+
// The layout is:
119+
// int exceptionCode (EX_SERVICE_SPECIFIC)
120+
// int serviceSpecificErrorCode
121+
// String errorMessage
122+
parcel.writeInt(-8)
123+
parcel.writeInt(errorCode)
124+
parcel.writeString(message)
125+
} catch (e: Exception) {
126+
parcel.recycle()
127+
throw e
128+
}
129+
130+
// The returned Parcel will be written to the Binder reply by BinderInterceptor.
131+
// On the client side, this will be decoded and rethrown as a ServiceSpecificException.
132+
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
133+
}
99134
}

app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,19 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
107107
if (data.readInt() == 1) {
108108
keymasterArgs.readFromParcel(data)
109109
}
110+
// Validate Attestation Challenge length in generateKey args
111+
val challenge =
112+
keymasterArgs.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
113+
if (challenge.size > 128) {
114+
SystemLogger.info(
115+
"[TX_ID: $txId] Rejecting generateKey: attestation challenge length exceeds 128 bytes (${challenge.size})."
116+
)
117+
val reply = Parcel.obtain()
118+
reply.writeNoException()
119+
// Return KM_ERROR_INVALID_INPUT_LENGTH (-21)
120+
reply.writeInt(-21)
121+
return TransactionResult.OverrideReply(reply)
122+
}
110123
keygenParameters[keyId] =
111124
LegacyKeygenParameters.fromKeymasterArguments(keymasterArgs)
112125

@@ -229,7 +242,17 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
229242
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
230243
ByteArray(0),
231244
)
232-
params.attestationChallenge = challenge
245+
// Validate Attestation Challenge length in attestKey args
246+
if (challenge.size > 128) {
247+
SystemLogger.info(
248+
"[TX_ID: $txId] Rejecting attestKey: attestation challenge length exceeds 128 bytes (${challenge.size})."
249+
)
250+
val reply = Parcel.obtain()
251+
reply.writeNoException()
252+
// Return KM_ERROR_INVALID_INPUT_LENGTH (-21)
253+
reply.writeInt(-21)
254+
return TransactionResult.OverrideReply(reply)
255+
}
233256
params.attestationChallenge = challenge
234257
}
235258

@@ -435,6 +458,7 @@ private data class LegacyKeygenParameters(
435458
* The RSA public exponent is not accessible via a public API in KeymasterArguments, so we
436459
* must use reflection to extract it.
437460
*/
461+
@SuppressLint("SoonBlockedPrivateApi")
438462
private fun getRsaExponent(args: KeymasterArguments): BigInteger? {
439463
return runCatching {
440464
val getArgumentByTag =

app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,19 @@ class KeyMintSecurityLevelInterceptor(
229229
)
230230
val params = data.createTypedArray(KeyParameter.CREATOR)!!
231231
val parsedParams = KeyMintAttestation(params)
232+
// Validate the length of TAG_ATTESTATION_CHALLENGE.
233+
// The Android KeyMint / KeyStore specification defines a maximum length of
234+
// 128 bytes for the attestation challenge.
235+
// If the provided challenge exceeds this limit, the operation must fail with
236+
// ErrorCode.INVALID_INPUT_LENGTH (-21).
237+
val challenge = parsedParams.attestationChallenge
238+
if (challenge != null && challenge.size > 128) {
239+
SystemLogger.info(
240+
"Rejecting generateKey: attestation challenge length exceeds 128 bytes (${challenge.size})."
241+
)
242+
// Return INVALID_INPUT_LENGTH as defined by the KeyMint error codes.
243+
return InterceptorUtils.createErrorReply(-21, "Attestation challenge too large")
244+
}
232245
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
233246
val isAttestKeyRequest =
234247
parsedParams.purpose.size == 1 &&

0 commit comments

Comments
 (0)