|
1 | 1 | package org.matrix.TEESimulator.interception.keystore.shim |
2 | 2 |
|
3 | 3 | import android.hardware.security.keymint.Algorithm |
| 4 | +import android.hardware.security.keymint.BlockMode |
4 | 5 | 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 |
7 | 8 | import android.os.RemoteException |
8 | 9 | import android.system.keystore2.IKeystoreOperation |
9 | | -import java.security.PrivateKey |
| 10 | +import java.security.KeyPair |
10 | 11 | import java.security.Signature |
| 12 | +import java.security.SignatureException |
| 13 | +import javax.crypto.Cipher |
11 | 14 | import org.matrix.TEESimulator.attestation.KeyMintAttestation |
12 | | -import org.matrix.TEESimulator.interception.keystore.InterceptorUtils |
| 15 | +import org.matrix.TEESimulator.logging.KeyMintParameterLogger |
13 | 16 | import org.matrix.TEESimulator.logging.SystemLogger |
14 | 17 |
|
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? |
25 | 21 |
|
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? |
32 | 23 |
|
33 | | - signature = Signature.getInstance(signatureAlgorithm).apply { initSign(privateKey) } |
34 | | - } |
| 24 | + fun abort() |
| 25 | +} |
35 | 26 |
|
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 = |
42 | 31 | when (params.digest.firstOrNull()) { |
43 | 32 | Digest.SHA_2_256 -> "SHA256" |
44 | 33 | Digest.SHA_2_384 -> "SHA384" |
45 | 34 | 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" |
48 | 36 | } |
49 | | - |
50 | | - val algorithmName = |
| 37 | + val keyAlgo = |
51 | 38 | when (params.algorithm) { |
52 | 39 | Algorithm.EC -> "ECDSA" |
53 | 40 | Algorithm.RSA -> "RSA" |
54 | | - // Add other algorithm mappings as needed |
55 | 41 | else -> |
56 | 42 | 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}" |
58 | 57 | ) |
59 | 58 | } |
| 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 | +} |
60 | 97 |
|
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 | + } |
62 | 169 | } |
63 | 170 |
|
64 | | - fun update(data: ByteArray?) { |
65 | | - if (data == null || data.isEmpty()) return |
| 171 | + fun update(data: ByteArray?): ByteArray? { |
66 | 172 | try { |
67 | | - signature.update(data) |
| 173 | + return primitive.update(data) |
68 | 174 | } 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) |
70 | 176 | throw e |
71 | 177 | } |
72 | 178 | } |
73 | 179 |
|
74 | | - fun finish(data: ByteArray?): ByteArray { |
75 | | - update(data) |
| 180 | + fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? { |
76 | 181 | 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.") |
81 | 184 | return result |
82 | 185 | } 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. |
84 | 188 | throw e |
85 | 189 | } |
86 | 190 | } |
87 | 191 |
|
88 | 192 | fun abort() { |
| 193 | + primitive.abort() |
89 | 194 | SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.") |
90 | 195 | } |
91 | 196 | } |
92 | 197 |
|
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() { |
97 | 201 |
|
98 | 202 | @Throws(RemoteException::class) |
99 | 203 | 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) |
103 | 205 | } |
104 | 206 |
|
105 | 207 | @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) |
109 | 210 | } |
110 | 211 |
|
111 | 212 | @Throws(RemoteException::class) |
|
0 commit comments