Skip to content

Commit f01259a

Browse files
committed
feat: implement createOperation for ISecurityLevel experimentally
Signed-off-by: qwq233 <[email protected]>
1 parent 2db6d0b commit f01259a

6 files changed

Lines changed: 349 additions & 6 deletions

File tree

service/src/main/java/io/github/a13e300/tricky_store/Cache.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,20 @@ object Cache {
3131

3232
// generated key section
3333
data class Key(val uid: Int, val alias: String)
34-
data class Info(val keyPair: KeyPair, val chain: List<Certificate>, val response: KeyEntryResponse)
34+
data class Info(val key: Key, val keyPair: KeyPair, val chain: List<Certificate>, val response: KeyEntryResponse)
3535

3636
private val keys = ConcurrentHashMap<Key, Info>()
3737

3838
fun putKey(uid: Int, alias: String, keyPair: KeyPair, chain: List<Certificate>, response: KeyEntryResponse) {
39-
keys[Key(uid, alias)] = Info(keyPair, chain, response)
39+
keys[Key(uid, alias)] = Info(Key(uid, alias), keyPair, chain, response)
4040
}
4141

4242
fun putKey(key: Key, info: Info) {
4343
keys[key] = info
4444
}
4545

46+
fun getInfoByNspace(nspace: Long): Info? = keys.values.firstOrNull { it.response.metadata?.key?.nspace == nspace }
47+
4648
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? = keys[Key(uid, alias)]?.response
4749

4850
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? = keys[Key(uid, alias)]?.let { Pair(it.keyPair, it.chain) }

service/src/main/java/io/github/a13e300/tricky_store/SecurityLevelInterceptor.kt

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ import android.hardware.security.keymint.Tag
77
import android.os.IBinder
88
import android.os.Parcel
99
import android.system.keystore2.Authorization
10+
import android.system.keystore2.CreateOperationResponse
11+
import android.system.keystore2.IKeystoreOperation
1012
import android.system.keystore2.IKeystoreSecurityLevel
1113
import android.system.keystore2.KeyDescriptor
1214
import android.system.keystore2.KeyEntryResponse
1315
import android.system.keystore2.KeyMetadata
14-
import io.github.a13e300.tricky_store.Cache.Info
15-
import io.github.a13e300.tricky_store.Cache.Key
1616
import io.github.a13e300.tricky_store.binder.BinderInterceptor
1717
import io.github.a13e300.tricky_store.keystore.CertHack
1818
import io.github.a13e300.tricky_store.keystore.Utils
1919
import java.security.KeyFactory
20+
import java.security.PrivateKey
21+
import java.security.Signature
2022
import java.security.cert.Certificate
2123

2224
class SecurityLevelInterceptor(
@@ -48,7 +50,7 @@ class SecurityLevelInterceptor(
4850
// Logger.e("warn: attestation key not supported now")
4951
val pair = CertHack.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp) ?: return@runCatching
5052
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
51-
Cache.putKey(Key(callingUid, keyDescriptor.alias), Info(pair.first, pair.second, response))
53+
Cache.putKey(callingUid, keyDescriptor.alias, pair.first, pair.second, response)
5254
val p = Parcel.obtain()
5355
p.writeNoException()
5456
p.writeTypedObject(response.metadata, 0)
@@ -94,7 +96,7 @@ class SecurityLevelInterceptor(
9496
Pair(pair.first.first, pair.second)
9597
}
9698
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
97-
Cache.putKey(Key(callingUid, keyDescriptor.alias), Info(pair.first, pair.second, response))
99+
Cache.putKey(callingUid, keyDescriptor.alias, pair.first, pair.second, response)
98100

99101
Logger.d("imported key generated uid=$callingUid alias=${keyDescriptor.alias}")
100102
}
@@ -105,14 +107,78 @@ class SecurityLevelInterceptor(
105107
}
106108

107109
createOperationTransaction -> runCatching {
110+
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
111+
Logger.d("createOperationTransaction uid=$callingUid pid=$callingPid")
112+
113+
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return Skip
114+
val params = data.createTypedArray(KeyParameter.CREATOR) ?: return Skip
115+
val kgp = CertHack.KeyGenParameters(params)
116+
117+
val info = Cache.getInfoByNspace(keyDescriptor.nspace)
118+
if (info == null) {
119+
return Skip
120+
}
121+
if (keyDescriptor.domain != 4) throw IllegalArgumentException("unsupported domain ${keyDescriptor.domain}")
122+
kgp.purpose.any { it != 2 /* sign */ && it != 7 /* attest */ } ||
123+
throw IllegalArgumentException("unsupported purpose ${kgp.purpose}")
124+
kgp.digest.any { it != 4 } ||
125+
throw IllegalArgumentException("unsupported digest ${kgp.digest}")
126+
val algorithm = when (kgp.algorithm) {
127+
Algorithm.EC -> "SHA256withECDSA"
128+
Algorithm.RSA -> "SHA256withRSA"
129+
else -> throw IllegalArgumentException("unsupported algorithm ${kgp.algorithm}")
130+
}
131+
if (info.key.uid != callingUid) {
132+
Logger.e("key uid mismatch ${info.key.uid} != $callingUid")
133+
return Skip
134+
}
108135

136+
val op = KeyStoreOperation(info.keyPair.private, algorithm)
137+
val parcel = Parcel.obtain()
138+
parcel.writeNoException()
139+
val createOperationResponse = CreateOperationResponse().apply {
140+
iOperation = op
141+
}
142+
parcel.writeTypedObject(createOperationResponse, 0)
143+
144+
return OverrideReply(0, parcel)
109145
}.onFailure {
110146
Logger.e("", it)
111147
}
112148
}
113149
return Skip
114150
}
115151

152+
private class KeyStoreOperation : IKeystoreOperation.Stub {
153+
val signature: Signature
154+
var isAborted = false
155+
156+
constructor(privateKey: PrivateKey, algorithm: String) {
157+
signature = Signature.getInstance(algorithm)
158+
signature.initSign(privateKey)
159+
}
160+
161+
override fun updateAad(aadInput: ByteArray?) {
162+
// do nothing for now
163+
}
164+
165+
override fun update(input: ByteArray): ByteArray? {
166+
if (isAborted) throw IllegalStateException("operation aborted")
167+
signature.update(input)
168+
return null
169+
}
170+
171+
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
172+
if (isAborted) throw IllegalStateException("operation aborted")
173+
this.signature.update(input)
174+
return this.signature.sign()
175+
}
176+
177+
override fun abort() {
178+
isAborted = true
179+
}
180+
}
181+
116182
private fun buildResponse(
117183
chain: List<Certificate>,
118184
params: CertHack.KeyGenParameters,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Decompiled by FernFlower - 648ms
3+
//
4+
package android.system.keystore2;
5+
6+
import android.os.Parcel;
7+
import android.os.Parcelable;
8+
9+
public class CreateOperationResponse implements Parcelable {
10+
public static final Creator<CreateOperationResponse> CREATOR = new Creator<>() {
11+
@Override
12+
public CreateOperationResponse createFromParcel(Parcel in) {
13+
throw new RuntimeException();
14+
}
15+
16+
@Override
17+
public CreateOperationResponse[] newArray(int size) {
18+
throw new RuntimeException();
19+
}
20+
};
21+
public IKeystoreOperation iOperation;
22+
public OperationChallenge operationChallenge;
23+
public KeyParameters parameters;
24+
public byte[] upgradedBlob;
25+
26+
private int describeContents(Object var1) {
27+
throw new RuntimeException("Stub!");
28+
}
29+
30+
public int describeContents() {
31+
throw new RuntimeException("Stub!");
32+
}
33+
34+
public final int getStability() {
35+
throw new RuntimeException("Stub!");
36+
}
37+
38+
public final void readFromParcel(Parcel var1) {
39+
throw new RuntimeException("Stub!");
40+
}
41+
42+
public final void writeToParcel(Parcel var1, int var2) {
43+
throw new RuntimeException("Stub!");
44+
}
45+
}
46+
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package android.system.keystore2;
2+
3+
import android.os.IBinder;
4+
import android.os.IInterface;
5+
6+
import androidx.annotation.Nullable;
7+
8+
/**
9+
* {@code IKeystoreOperation} represents a cryptographic operation using a Keystore key.
10+
*
11+
* <p>The lifecycle of an operation begins with {@link KeystoreSecurityLevel#create}
12+
* and ends with a call to {@link #finish}, {@link #abort}, or when the reference to
13+
* the binder object is released.
14+
*
15+
* <p>During the operation lifecycle, {@link #update} may be called multiple times.
16+
* For AEAD operations, {@link #updateAad} may be called to add associated data, but
17+
* it must be called before the first call to {@link #update}.
18+
*
19+
* <h2>Error Conditions</h2>
20+
* <p>Error conditions are reported as service-specific errors:
21+
* <ul>
22+
* <li>Positive error codes correspond to {@code android.system.keystore2.ResponseCode}
23+
* and indicate error conditions diagnosed by the Keystore 2.0 service.</li>
24+
* <li>Negative error codes correspond to {@code android.hardware.security.keymint.ErrorCode}
25+
* and indicate KeyMint backend errors. Refer to the KeyMint interface specification
26+
* for detailed information.</li>
27+
* </ul>
28+
*/
29+
public interface IKeystoreOperation extends IInterface {
30+
String DESCRIPTOR = "android.system.keystore2.IKeystoreOperation";
31+
32+
/**
33+
* Advances an operation by adding Additional Authenticated Data (AAD) to AEAD mode
34+
* encryption or decryption operations. This method cannot be called after {@link #update},
35+
* and attempting to do so will result in {@code ErrorCode.INVALID_TAG}. This error code
36+
* is used for historical reasons, dating back when AAD was passed as an additional
37+
* {@code KeyParameter} with the tag {@code ASSOCIATED_DATA}.
38+
*
39+
* <h2>Error Conditions</h2>
40+
* <ul>
41+
* <li>{@code ResponseCode.TOO_MUCH_DATA} if {@code aadInput} exceeds 32KiB.</li>
42+
* <li>{@code ResponseCode.OPERATION_BUSY} if {@code updateAad} is called concurrently
43+
* with any other {@code IKeystoreOperation} API call.</li>
44+
* <li>{@code ErrorCode.INVALID_TAG} if {@code updateAad} is called after {@link #update}
45+
* on a given operation.</li>
46+
* <li>{@code ErrorCode.INVALID_OPERATION_HANDLE} if the operation has been finalized
47+
* for any reason.</li>
48+
* </ul>
49+
* <p>
50+
* Note: Any error condition except {@code ResponseCode.OPERATION_BUSY} finalizes the
51+
* operation, causing subsequent API calls to return {@code INVALID_OPERATION_HANDLE}.
52+
*
53+
* @param aadInput the Additional Authenticated Data to be added to the operation
54+
*/
55+
void updateAad(byte[] aadInput);
56+
57+
/**
58+
* Advances the operation by processing additional input data. The input data may be
59+
* plain text to be encrypted or signed, or cipher text to be decrypted. During
60+
* encryption operations, this method returns the resulting cipher text. During
61+
* decryption operations, it returns the resulting plain text. No data is returned
62+
* for signing operations.
63+
*
64+
* <h2>Error Conditions</h2>
65+
* <ul>
66+
* <li>{@code ResponseCode.TOO_MUCH_DATA} if the {@code input} exceeds 32KiB.</li>
67+
* <li>{@code ResponseCode.OPERATION_BUSY} if {@code updateAad} is called concurrently
68+
* with any other {@code IKeystoreOperation} API call.</li>
69+
* <li>{@code ErrorCode.INVALID_OPERATION_HANDLE} if the operation has been finalized
70+
* for any reason.</li>
71+
* </ul>
72+
* <p>
73+
* Note: Any error condition except {@code ResponseCode.OPERATION_BUSY} finalizes the
74+
* operation, causing subsequent API calls to return {@code INVALID_OPERATION_HANDLE}.
75+
*
76+
* @param input the input data to process
77+
* @return the output data, which may be cipher text during encryption, plain text
78+
* during decryption, or {@code null} for signing operations
79+
*/
80+
byte[] update(byte[] input);
81+
82+
/**
83+
* Finalizes the operation. This method takes a final chunk of input data similar to
84+
* {@link #update}. The output varies depending on the operation type: it may be a
85+
* signature for signing operations, plain text for decryption operations, or cipher
86+
* text for encryption operations.
87+
*
88+
* <h2>Error Conditions</h2>
89+
* <ul>
90+
* <li>{@code ResponseCode.TOO_MUCH_DATA} if the {@code input} exceeds 32KiB.</li>
91+
* <li>{@code ResponseCode.OPERATION_BUSY} if {@code updateAad} is called concurrently
92+
* with any other {@code IKeystoreOperation} API call.</li>
93+
* <li>{@code ErrorCode.INVALID_OPERATION_HANDLE} if the operation has already been
94+
* finalized for any reason.</li>
95+
* </ul>
96+
* <p>
97+
* Note: {@code finish} finalizes the operation regardless of the outcome, unless
98+
* {@code ResponseCode.OPERATION_BUSY} is returned.
99+
*
100+
* @param input the final chunk of input data to process
101+
* @param signature an optional HMAC signature for HMAC verification operations
102+
* @return the operation result, which may be a signature for signing operations,
103+
* an AEAD message tag for authenticated encryption, or the final chunk of
104+
* cipher/plain text for encryption/decryption operations respectively
105+
*/
106+
byte[] finish(@Nullable byte[] input, @Nullable byte[] signature);
107+
108+
/**
109+
* Aborts the operation immediately.
110+
*
111+
* <p>Note: {@code abort} finalizes the operation regardless of the outcome, unless
112+
* {@code ResponseCode.OPERATION_BUSY} is returned.
113+
*/
114+
void abort();
115+
116+
class Stub implements IKeystoreOperation {
117+
public static IKeystoreOperation asInterface(IBinder b) {
118+
throw new RuntimeException("Stub!");
119+
}
120+
121+
@Override
122+
public void updateAad(byte[] aadInput) {
123+
throw new RuntimeException("Stub!");
124+
}
125+
126+
@Override
127+
public byte[] update(byte[] input) {
128+
throw new RuntimeException("Stub!");
129+
}
130+
131+
@Override
132+
public byte[] finish(@Nullable byte[] input, @Nullable byte[] signature) {
133+
throw new RuntimeException("Stub!");
134+
}
135+
136+
@Override
137+
public void abort() {
138+
throw new RuntimeException("Stub!");
139+
}
140+
141+
@Override
142+
public IBinder asBinder() {
143+
throw new RuntimeException("Stub!");
144+
}
145+
}
146+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// Decompiled by FernFlower - 531ms
3+
//
4+
package android.system.keystore2;
5+
6+
import android.hardware.security.keymint.KeyParameter;
7+
import android.os.Parcel;
8+
import android.os.Parcelable;
9+
10+
public class KeyParameters implements Parcelable {
11+
public static final Creator<KeyParameters> CREATOR = new Creator<>() {
12+
@Override
13+
public KeyParameters createFromParcel(Parcel in) {
14+
throw new RuntimeException();
15+
}
16+
17+
@Override
18+
public KeyParameters[] newArray(int size) {
19+
throw new RuntimeException();
20+
}
21+
};
22+
public KeyParameter[] keyParameter;
23+
24+
private int describeContents(Object var1) {
25+
throw new RuntimeException("Stub!");
26+
}
27+
28+
public int describeContents() {
29+
throw new RuntimeException("Stub!");
30+
}
31+
32+
public final int getStability() {
33+
throw new RuntimeException("Stub!");
34+
}
35+
36+
public final void readFromParcel(Parcel var1) {
37+
throw new RuntimeException("Stub!");
38+
}
39+
40+
public final void writeToParcel(Parcel var1, int var2) {
41+
throw new RuntimeException("Stub!");
42+
}
43+
}
44+

0 commit comments

Comments
 (0)