Skip to content

Commit 2b3e738

Browse files
committed
Implement multi-purpose crypto operations
This commit refactors the `SoftwareOperation` engine to support multiple cryptographic purposes beyond just signing. The simulator can now correctly handle `SIGN`, `VERIFY`, `ENCRYPT`, and `DECRYPT` operations based on the parameters provided by the client application. This is a significant architectural improvement that moves from a hardcoded implementation to a flexible, strategy-based pattern: 1. Strategy Pattern for Crypto: A `CryptoPrimitive` sealed interface was introduced to define a common API for cryptographic actions. Concrete implementations (`Signer`, `Verifier`, `CipherPrimitive`) now encapsulate the specific logic for each purpose. The main `SoftwareOperation` class acts as a controller, instantiating the correct primitive based on the `KeyPurpose` tag from the operation parameters. 2. JCA Algorithm Mapping: A `JcaAlgorithmMapper` object was created to centralize the complex logic of converting KeyMint constants into standard JCA algorithm strings (e.g., "SHA256withECDSA", "RSA/ECB/PKCS1Padding"). This makes the code cleaner and more maintainable. 3. Expanded Parameter Parsing: To support cipher operations, `KeyMintAttestation` has been updated to parse `BLOCK_MODE` and `PADDING` tags. The `KeyMintParameterLogger` was also enhanced to provide human-readable names for these new modes. This refactoring makes the `createOperation` simulation far more robust and accurate, correctly mimicking the behavior of a real KeyMint implementation across a wider range of cryptographic use cases.
1 parent 5c65c67 commit 2b3e738

File tree

6 files changed

+214
-66
lines changed

6 files changed

+214
-66
lines changed

app/src/main/java/org/matrix/TEESimulator/attestation/KeyMintAttestation.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package org.matrix.TEESimulator.attestation
22

3-
import android.hardware.security.keymint.EcCurve
4-
import android.hardware.security.keymint.KeyParameter
5-
import android.hardware.security.keymint.Tag
3+
import android.hardware.security.keymint.*
64
import java.math.BigInteger
75
import java.util.Date
86
import javax.security.auth.x500.X500Principal
@@ -22,6 +20,8 @@ data class KeyMintAttestation(
2220
val algorithm: Int,
2321
val ecCurve: Int,
2422
val ecCurveName: String,
23+
val blockMode: List<Int>,
24+
val padding: List<Int>,
2525
val purpose: List<Int>,
2626
val digest: List<Int>,
2727
val rsaPublicExponent: BigInteger?,
@@ -54,6 +54,12 @@ data class KeyMintAttestation(
5454
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
5555
ecCurveName = params.deriveEcCurveName(),
5656

57+
// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
58+
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),
59+
60+
// AOSP: [key_param(tag = PADDING, field = PaddingMode)]
61+
padding = params.findAllPaddingMode(Tag.PADDING),
62+
5763
// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
5864
purpose = params.findAllKeyPurpose(Tag.PURPOSE),
5965

@@ -121,6 +127,14 @@ private fun Array<KeyParameter>.findDate(tag: Int): Date? =
121127
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
122128
this.find { it.tag == tag }?.value?.blob
123129

130+
/** Maps to AOSP field = BlockMode (Repeated) */
131+
private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> =
132+
this.filter { it.tag == tag }.map { it.value.blockMode }
133+
134+
/** Maps to AOSP field = BlockMode (Repeated) */
135+
private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> =
136+
this.filter { it.tag == tag }.map { it.value.paddingMode }
137+
124138
/** Maps to AOSP field = KeyPurpose (Repeated) */
125139
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
126140
this.filter { it.tag == tag }.map { it.value.keyPurpose }

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ private data class LegacyKeygenParameters(
378378
algorithm = this.algorithm,
379379
ecCurve = 0, // Not explicitly available in legacy args, but not critical
380380
ecCurveName = this.ecCurveName ?: "",
381+
blockMode = listOf<Int>(),
382+
padding = listOf<Int>(),
381383
purpose = this.purpose,
382384
digest = this.digest,
383385
rsaPublicExponent = this.rsaPublicExponent,

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

Lines changed: 157 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,212 @@
11
package org.matrix.TEESimulator.interception.keystore.shim
22

33
import android.hardware.security.keymint.Algorithm
4+
import android.hardware.security.keymint.BlockMode
45
import android.hardware.security.keymint.Digest
5-
import android.os.Binder
6-
import android.os.Parcel
6+
import android.hardware.security.keymint.KeyPurpose
7+
import android.hardware.security.keymint.PaddingMode
78
import android.os.RemoteException
89
import android.system.keystore2.IKeystoreOperation
9-
import java.security.PrivateKey
10+
import java.security.KeyPair
1011
import java.security.Signature
12+
import java.security.SignatureException
13+
import javax.crypto.Cipher
1114
import org.matrix.TEESimulator.attestation.KeyMintAttestation
12-
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
15+
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
1316
import org.matrix.TEESimulator.logging.SystemLogger
1417

15-
/**
16-
* A software-only implementation of a cryptographic operation (e.g., signing). This class uses the
17-
* standard Java JCA to perform operations on a given private key.
18-
*/
19-
class SoftwareOperation(
20-
private val txId: Long,
21-
privateKey: PrivateKey,
22-
params: KeyMintAttestation,
23-
) {
24-
private val signature: Signature
18+
// A sealed interface to represent the different cryptographic operations we can perform.
19+
private sealed interface CryptoPrimitive {
20+
fun update(data: ByteArray?): ByteArray?
2521

26-
init {
27-
// Parse the KeyMintAttestation object to determine the correct JCA algorithm string.
28-
val signatureAlgorithm = parseSignatureAlgorithm(params)
29-
SystemLogger.debug(
30-
"[SoftwareOp TX_ID: $txId] Initializing with algorithm: $signatureAlgorithm"
31-
)
22+
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
3223

33-
signature = Signature.getInstance(signatureAlgorithm).apply { initSign(privateKey) }
34-
}
24+
fun abort()
25+
}
3526

36-
/**
37-
* Determines the JCA standard signature algorithm string from KeyMint parameters. Replicates
38-
* logic seen in AOSP frameworks like CertificateGenerator.
39-
*/
40-
private fun parseSignatureAlgorithm(params: KeyMintAttestation): String {
41-
val digestName =
27+
// Helper object to map KeyMint constants to JCA algorithm strings.
28+
private object JcaAlgorithmMapper {
29+
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
30+
val digest =
4231
when (params.digest.firstOrNull()) {
4332
Digest.SHA_2_256 -> "SHA256"
4433
Digest.SHA_2_384 -> "SHA384"
4534
Digest.SHA_2_512 -> "SHA512"
46-
// Add other digest mappings as needed
47-
else -> "NONE" // A valid JCA value for certain algorithms
35+
else -> "NONE"
4836
}
49-
50-
val algorithmName =
37+
val keyAlgo =
5138
when (params.algorithm) {
5239
Algorithm.EC -> "ECDSA"
5340
Algorithm.RSA -> "RSA"
54-
// Add other algorithm mappings as needed
5541
else ->
5642
throw IllegalArgumentException(
57-
"Unsupported algorithm for signing: ${params.algorithm}"
43+
"Unsupported signature algorithm: ${params.algorithm}"
44+
)
45+
}
46+
return "${digest}with${keyAlgo}"
47+
}
48+
49+
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
50+
val keyAlgo =
51+
when (params.algorithm) {
52+
Algorithm.RSA -> "RSA"
53+
Algorithm.AES -> "AES"
54+
else ->
55+
throw IllegalArgumentException(
56+
"Unsupported cipher algorithm: ${params.algorithm}"
5857
)
5958
}
59+
val blockMode =
60+
when (params.blockMode.firstOrNull()) {
61+
BlockMode.ECB -> "ECB"
62+
BlockMode.CBC -> "CBC"
63+
BlockMode.GCM -> "GCM"
64+
else -> "ECB" // Default for RSA
65+
}
66+
val padding =
67+
when (params.padding.firstOrNull()) {
68+
PaddingMode.NONE -> "NoPadding"
69+
PaddingMode.PKCS7 -> "PKCS7Padding"
70+
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
71+
PaddingMode.RSA_OAEP -> "OAEPPadding"
72+
else -> "NoPadding" // Default for GCM
73+
}
74+
return "$keyAlgo/$blockMode/$padding"
75+
}
76+
}
77+
78+
// Concrete implementation for Signing.
79+
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
80+
private val signature: Signature =
81+
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
82+
initSign(keyPair.private)
83+
}
84+
85+
override fun update(data: ByteArray?): ByteArray? {
86+
if (data != null) signature.update(data)
87+
return null
88+
}
89+
90+
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray {
91+
if (data != null) update(data)
92+
return this.signature.sign()
93+
}
94+
95+
override fun abort() {}
96+
}
6097

61-
return "${digestName}with${algorithmName}"
98+
// Concrete implementation for Verification.
99+
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
100+
private val signature: Signature =
101+
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
102+
initVerify(keyPair.public)
103+
}
104+
105+
override fun update(data: ByteArray?): ByteArray? {
106+
if (data != null) signature.update(data)
107+
return null
108+
}
109+
110+
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
111+
if (data != null) update(data)
112+
if (signature == null) throw SignatureException("Signature to verify is null")
113+
if (!this.signature.verify(signature)) {
114+
// Throwing an exception is how Keystore signals verification failure.
115+
throw SignatureException("Signature verification failed")
116+
}
117+
// A successful verification returns no data.
118+
return null
119+
}
120+
121+
override fun abort() {}
122+
}
123+
124+
// Concrete implementation for Encryption/Decryption.
125+
private class CipherPrimitive(
126+
keyPair: KeyPair,
127+
params: KeyMintAttestation,
128+
private val opMode: Int,
129+
) : CryptoPrimitive {
130+
private val cipher: Cipher =
131+
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
132+
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
133+
init(opMode, key)
134+
}
135+
136+
override fun update(data: ByteArray?): ByteArray? =
137+
if (data != null) cipher.update(data) else null
138+
139+
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
140+
if (data != null) cipher.doFinal(data) else cipher.doFinal()
141+
142+
override fun abort() {}
143+
}
144+
145+
/**
146+
* A software-only implementation of a cryptographic operation. This class acts as a controller,
147+
* delegating to a specific cryptographic primitive based on the operation's purpose.
148+
*/
149+
class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) {
150+
// This now holds the specific strategy object (Signer, Verifier, etc.)
151+
private val primitive: CryptoPrimitive
152+
153+
init {
154+
// The "Strategy" pattern: choose the implementation based on the purpose.
155+
// For simplicity, we only consider the first purpose listed.
156+
val purpose = params.purpose.firstOrNull()
157+
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
158+
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.")
159+
160+
primitive =
161+
when (purpose) {
162+
KeyPurpose.SIGN -> Signer(keyPair, params)
163+
KeyPurpose.VERIFY -> Verifier(keyPair, params)
164+
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
165+
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
166+
else ->
167+
throw UnsupportedOperationException("Unsupported operation purpose: $purpose")
168+
}
62169
}
63170

64-
fun update(data: ByteArray?) {
65-
if (data == null || data.isEmpty()) return
171+
fun update(data: ByteArray?): ByteArray? {
66172
try {
67-
signature.update(data)
173+
return primitive.update(data)
68174
} catch (e: Exception) {
69-
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update signature.", e)
175+
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
70176
throw e
71177
}
72178
}
73179

74-
fun finish(data: ByteArray?): ByteArray {
75-
update(data)
180+
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
76181
try {
77-
val result = signature.sign()
78-
SystemLogger.info(
79-
"[SoftwareOp TX_ID: $txId] Finished signing. Signature size: ${result.size} bytes."
80-
)
182+
val result = primitive.finish(data, signature)
183+
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
81184
return result
82185
} catch (e: Exception) {
83-
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish signing.", e)
186+
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
187+
// Re-throw the exception so the binder can report it to the client.
84188
throw e
85189
}
86190
}
87191

88192
fun abort() {
193+
primitive.abort()
89194
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
90195
}
91196
}
92197

93-
/**
94-
* The Binder interface for our [SoftwareOperation].
95-
*/
96-
class SoftwareOperationBinder(private val operation: SoftwareOperation) : IKeystoreOperation.Stub() {
198+
/** The Binder interface for our [SoftwareOperation]. */
199+
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
200+
IKeystoreOperation.Stub() {
97201

98202
@Throws(RemoteException::class)
99203
override fun update(input: ByteArray?): ByteArray? {
100-
operation.update(input)
101-
// As per the AIDL comments, the update method for a signing operation returns nothing.
102-
return null
204+
return operation.update(input)
103205
}
104206

105207
@Throws(RemoteException::class)
106-
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray {
107-
// The 'signature' parameter is for verification operations, so we ignore it here.
108-
return operation.finish(input)
208+
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
209+
return operation.finish(input, signature)
109210
}
110211

111212
@Throws(RemoteException::class)

app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
package org.matrix.TEESimulator.logging
22

3-
import android.hardware.security.keymint.Algorithm
4-
import android.hardware.security.keymint.Digest
5-
import android.hardware.security.keymint.EcCurve
6-
import android.hardware.security.keymint.KeyParameter
7-
import android.hardware.security.keymint.KeyPurpose
8-
import android.hardware.security.keymint.Tag
3+
import android.hardware.security.keymint.*
94
import java.math.BigInteger
105
import java.nio.charset.StandardCharsets
116
import java.util.Date
@@ -34,7 +29,23 @@ object KeyMintParameterLogger {
3429
.associate { field -> (field.get(null) as Int) to field.name }
3530
}
3631

37-
private val purposeNames: Map<Int, String> by lazy {
32+
val blockModeNames: Map<Int, String> by lazy {
33+
BlockMode::class
34+
.java
35+
.fields
36+
.filter { it.type == Int::class.java }
37+
.associate { field -> (field.get(null) as Int) to field.name }
38+
}
39+
40+
val paddingNames: Map<Int, String> by lazy {
41+
PaddingMode::class
42+
.java
43+
.fields
44+
.filter { it.type == Int::class.java }
45+
.associate { field -> (field.get(null) as Int) to field.name }
46+
}
47+
48+
val purposeNames: Map<Int, String> by lazy {
3849
KeyPurpose::class
3950
.java
4051
.fields
@@ -69,7 +80,9 @@ object KeyMintParameterLogger {
6980
val formattedValue: String =
7081
when (param.tag) {
7182
Tag.ALGORITHM -> algorithmNames[value.algorithm]
83+
Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
7284
Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
85+
Tag.PADDING -> paddingNames[value.paddingMode]
7386
Tag.PURPOSE -> purposeNames[value.keyPurpose]
7487
Tag.DIGEST -> digestNames[value.digest]
7588
Tag.AUTH_TIMEOUT,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package android.hardware.security.keymint;
2+
3+
public @interface BlockMode {
4+
public static final int ECB = 1;
5+
public static final int CBC = 2;
6+
public static final int CTR = 3;
7+
public static final int GCM = 32;
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package android.hardware.security.keymint;
2+
3+
public @interface PaddingMode {
4+
public static final int NONE = 1;
5+
public static final int RSA_OAEP = 2;
6+
public static final int RSA_PSS = 3;
7+
public static final int RSA_PKCS1_1_5_ENCRYPT = 4;
8+
public static final int RSA_PKCS1_1_5_SIGN = 5;
9+
public static final int PKCS7 = 64;
10+
}

0 commit comments

Comments
 (0)