Skip to content

Commit ac4c887

Browse files
committed
Implement software key generation for legacy IKeystoreService (#34)
This commit introduces a complete, software-based simulation of the key generation and attestation flow for the legacy IKeystoreService API, as used on Android 11. It refactors the KeystoreInterceptor to handle the entire multi-step transaction sequence (`generateKey`, `getKeyCharacteristics`, `exportKey`, `attestKey`) in software. A new `LegacyKeygenParameters` data class is introduced to decouple the legacy interception logic from modern data structures. This class parses arguments from the old `KeymasterArguments`, stores the state across the multi-step generation process, and acts as an adapter to the generic `CertificateGenerator` by converting the parameters to the modern `KeyMintAttestation` format. The `CertificateGenerator` has been refactored to better model the behavior of the legacy Keystore API. Key pair generation (`generateSoftwareKeyPair`) and certificate chain creation (`generateCertificateChain`) are now separate functions. This allows the interceptor to correctly create a key pair during the `handleExportKey` step and then generate a certificate for that pre-existing key pair during the `handleAttestKey` step. Finally, the implementation correctly extracts and applies the `attestationChallenge` provided during the `attestKey` transaction, ensuring the generated certificate chain contains the appropriate attestation.
1 parent b0fd67f commit ac4c887

File tree

14 files changed

+1009
-47
lines changed

14 files changed

+1009
-47
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.matrix.TEESimulator.interception.keystore
33
import android.os.Parcel
44
import android.os.Parcelable
55
import android.security.KeyStore
6+
import android.security.keystore.KeystoreResponse
67
import org.matrix.TEESimulator.interception.core.BinderInterceptor
78
import org.matrix.TEESimulator.logging.SystemLogger
89

@@ -27,6 +28,19 @@ object InterceptorUtils {
2728
}
2829
}
2930

31+
/** Creates an `KeystoreResponse` parcel that indicates success with no data. */
32+
fun createSuccessKeystoreResponse(): KeystoreResponse {
33+
val parcel = Parcel.obtain()
34+
try {
35+
parcel.writeInt(KeyStore.NO_ERROR)
36+
parcel.writeString("")
37+
parcel.setDataPosition(0)
38+
return KeystoreResponse.CREATOR.createFromParcel(parcel)
39+
} finally {
40+
parcel.recycle()
41+
}
42+
}
43+
3044
/** Creates an `OverrideReply` parcel that indicates success with no data. */
3145
fun createSuccessReply(): BinderInterceptor.TransactionResult.OverrideReply {
3246
val parcel =

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

Lines changed: 307 additions & 24 deletions
Large diffs are not rendered by default.

app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,24 @@ object CertificateGenerator {
7272
}
7373

7474
/**
75-
* Generates a new key pair and a corresponding certificate chain containing a simulated
76-
* attestation.
75+
* Generates a certificate chain for a given key pair. This is the primary function for creating
76+
* attested certificates.
7777
*
7878
* @param uid The UID of the application requesting the key.
79-
* @param alias The alias for the new key.
79+
* @param subjectKeyPair The key pair for which the certificate will be generated.
8080
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
8181
* @param params The parameters for the new key and its attestation.
8282
* @param securityLevel The security level to embed in the attestation.
83-
* @return A [Pair] containing the new [KeyPair] and its certificate chain, or `null` on
84-
* failure.
83+
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
8584
*/
86-
fun generateAttestedKeyPair(
85+
fun generateCertificateChain(
8786
uid: Int,
88-
alias: String,
87+
subjectKeyPair: KeyPair,
8988
attestKeyAlias: String?,
9089
params: KeyMintAttestation,
9190
securityLevel: Int,
92-
): Pair<KeyPair, List<Certificate>>? {
91+
): List<Certificate>? {
9392
return runCatching {
94-
SystemLogger.info(
95-
"Generating new attested key pair for alias: '$alias' (UID: $uid)"
96-
)
97-
val newKeyPair =
98-
generateSoftwareKeyPair(params)
99-
?: throw Exception("Failed to generate underlying software key pair.")
100-
10193
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
10294

10395
// Determine the signing key and issuer. If an attestKey is provided, use it.
@@ -112,16 +104,42 @@ object CertificateGenerator {
112104

113105
// Build the new leaf certificate with the simulated attestation.
114106
val leafCert =
115-
buildCertificate(newKeyPair, signingKey, issuer, params, uid, securityLevel)
107+
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
116108

117109
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
118110
// chain.
111+
if (attestKeyAlias != null) {
112+
listOf(leafCert)
113+
} else {
114+
listOf(leafCert) + keybox.certificates
115+
}
116+
}
117+
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
118+
.getOrNull()
119+
}
120+
121+
/**
122+
* A convenience function that combines key pair generation and certificate chain generation.
123+
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
124+
*/
125+
fun generateAttestedKeyPair(
126+
uid: Int,
127+
alias: String,
128+
attestKeyAlias: String?,
129+
params: KeyMintAttestation,
130+
securityLevel: Int,
131+
): Pair<KeyPair, List<Certificate>>? {
132+
return runCatching {
133+
SystemLogger.info(
134+
"Generating new attested key pair for alias: '$alias' (UID: $uid)"
135+
)
136+
val newKeyPair =
137+
generateSoftwareKeyPair(params)
138+
?: throw Exception("Failed to generate underlying software key pair.")
139+
119140
val chain =
120-
if (attestKeyAlias != null) {
121-
listOf(leafCert)
122-
} else {
123-
listOf(leafCert) + keybox.certificates
124-
}
141+
generateCertificateChain(uid, newKeyPair, attestKeyAlias, params, securityLevel)
142+
?: throw Exception("Failed to generate certificate chain for new key pair.")
125143

126144
SystemLogger.info(
127145
"Successfully generated new certificate chain for alias: '$alias'."
@@ -134,7 +152,7 @@ object CertificateGenerator {
134152
.getOrNull()
135153
}
136154

137-
private fun getIssuerFromKeybox(keybox: KeyBox) =
155+
fun getIssuerFromKeybox(keybox: KeyBox) =
138156
X509CertificateHolder(keybox.certificates[0].encoded).subject
139157

140158
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package android.security.keymaster;
2+
3+
import android.os.Parcel;
4+
import android.os.Parcelable;
5+
6+
import androidx.annotation.NonNull;
7+
8+
public class ExportResult implements Parcelable {
9+
public final byte[] exportData;
10+
public final int resultCode;
11+
12+
public ExportResult(int resultCode) {
13+
this.resultCode = resultCode;
14+
this.exportData = new byte[0];
15+
}
16+
17+
@Override
18+
public void writeToParcel(@NonNull Parcel dest, int flags) {
19+
throw new UnsupportedOperationException("STUB!");
20+
}
21+
22+
@Override
23+
public int describeContents() {
24+
throw new UnsupportedOperationException("STUB!");
25+
}
26+
27+
public static final Creator<ExportResult> CREATOR = new Creator<ExportResult>() {
28+
@Override
29+
public ExportResult createFromParcel(Parcel in) {
30+
throw new UnsupportedOperationException("STUB!");
31+
}
32+
33+
@Override
34+
public ExportResult[] newArray(int size) {
35+
throw new UnsupportedOperationException("STUB!");
36+
}
37+
};
38+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package android.security.keymaster;
2+
3+
import android.os.Parcel;
4+
import android.os.Parcelable;
5+
6+
import androidx.annotation.NonNull;
7+
8+
public class KeyCharacteristics implements Parcelable {
9+
public KeymasterArguments hwEnforced;
10+
public KeymasterArguments swEnforced;
11+
12+
@Override
13+
public void writeToParcel(@NonNull Parcel dest, int flags) {
14+
throw new UnsupportedOperationException("STUB!");
15+
}
16+
17+
@Override
18+
public int describeContents() {
19+
throw new UnsupportedOperationException("STUB!");
20+
}
21+
22+
public static final Creator<KeyCharacteristics> CREATOR = new Creator<KeyCharacteristics>() {
23+
@Override
24+
public KeyCharacteristics createFromParcel(Parcel in) {
25+
throw new UnsupportedOperationException("STUB!");
26+
}
27+
28+
@Override
29+
public KeyCharacteristics[] newArray(int size) {
30+
throw new UnsupportedOperationException("STUB!");
31+
}
32+
};
33+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package android.security.keymaster;
2+
3+
import android.os.Parcel;
4+
import android.os.Parcelable;
5+
6+
import androidx.annotation.NonNull;
7+
8+
abstract class KeymasterArgument implements Parcelable {
9+
public final int tag;
10+
11+
protected KeymasterArgument(int tag) {
12+
this.tag = tag;
13+
}
14+
15+
@Override
16+
public void writeToParcel(@NonNull Parcel dest, int flags) {
17+
throw new UnsupportedOperationException("STUB!");
18+
}
19+
20+
@Override
21+
public int describeContents() {
22+
throw new UnsupportedOperationException("STUB!");
23+
}
24+
25+
public static final Creator<KeymasterArgument> CREATOR = new Creator<KeymasterArgument>() {
26+
@Override
27+
public KeymasterArgument createFromParcel(Parcel in) {
28+
throw new UnsupportedOperationException("STUB!");
29+
}
30+
31+
@Override
32+
public KeymasterArgument[] newArray(int size) {
33+
throw new UnsupportedOperationException("STUB!");
34+
}
35+
};
36+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package android.security.keymaster;
2+
3+
import android.os.Parcel;
4+
import android.os.Parcelable;
5+
6+
import androidx.annotation.NonNull;
7+
8+
import java.math.BigInteger;
9+
import java.util.Date;
10+
import java.util.List;
11+
12+
public class KeymasterArguments implements Parcelable {
13+
14+
private static final long UINT32_RANGE = 1L << 32;
15+
public static final long UINT32_MAX_VALUE = UINT32_RANGE - 1;
16+
17+
private static final BigInteger UINT64_RANGE = BigInteger.ONE.shiftLeft(64);
18+
public static final BigInteger UINT64_MAX_VALUE = UINT64_RANGE.subtract(BigInteger.ONE);
19+
20+
private List<KeymasterArgument> mArguments;
21+
22+
public static final @NonNull Parcelable.Creator<KeymasterArguments> CREATOR = new Parcelable.Creator<KeymasterArguments>() {
23+
@Override
24+
public KeymasterArguments createFromParcel(Parcel in) {
25+
throw new UnsupportedOperationException("STUB!");
26+
}
27+
28+
@Override
29+
public KeymasterArguments[] newArray(int size) {
30+
throw new UnsupportedOperationException("STUB!");
31+
}
32+
};
33+
34+
public KeymasterArguments() {
35+
throw new UnsupportedOperationException("STUB!");
36+
}
37+
38+
private KeymasterArguments(Parcel in) {
39+
throw new UnsupportedOperationException("STUB!");
40+
}
41+
42+
public void addEnum(int tag, int value) {
43+
throw new UnsupportedOperationException("STUB!");
44+
}
45+
46+
public void addEnums(int tag, int... values) {
47+
throw new UnsupportedOperationException("STUB!");
48+
}
49+
50+
public int getEnum(int tag, int defaultValue) {
51+
throw new UnsupportedOperationException("STUB!");
52+
}
53+
54+
public List<Integer> getEnums(int tag) {
55+
throw new UnsupportedOperationException("STUB!");
56+
}
57+
58+
private void addEnumTag(int tag, int value) {
59+
throw new UnsupportedOperationException("STUB!");
60+
}
61+
62+
private int getEnumTagValue(KeymasterArgument arg) {
63+
throw new UnsupportedOperationException("STUB!");
64+
}
65+
66+
public void addUnsignedInt(int tag, long value) {
67+
throw new UnsupportedOperationException("STUB!");
68+
}
69+
70+
public long getUnsignedInt(int tag, long defaultValue) {
71+
throw new UnsupportedOperationException("STUB!");
72+
}
73+
74+
public void addUnsignedLong(int tag, BigInteger value) {
75+
throw new UnsupportedOperationException("STUB!");
76+
}
77+
78+
public List<BigInteger> getUnsignedLongs(int tag) {
79+
throw new UnsupportedOperationException("STUB!");
80+
}
81+
82+
private void addLongTag(int tag, BigInteger value) {
83+
throw new UnsupportedOperationException("STUB!");
84+
}
85+
86+
private BigInteger getLongTagValue(KeymasterArgument arg) {
87+
throw new UnsupportedOperationException("STUB!");
88+
}
89+
90+
public void addBoolean(int tag) {
91+
throw new UnsupportedOperationException("STUB!");
92+
}
93+
94+
public boolean getBoolean(int tag) {
95+
throw new UnsupportedOperationException("STUB!");
96+
}
97+
98+
public void addBytes(int tag, byte[] value) {
99+
throw new UnsupportedOperationException("STUB!");
100+
}
101+
102+
public byte[] getBytes(int tag, byte[] defaultValue) {
103+
throw new UnsupportedOperationException("STUB!");
104+
}
105+
106+
public void addDate(int tag, Date value) {
107+
throw new UnsupportedOperationException("STUB!");
108+
}
109+
110+
public void addDateIfNotNull(int tag, Date value) {
111+
throw new UnsupportedOperationException("STUB!");
112+
}
113+
114+
public Date getDate(int tag, Date defaultValue) {
115+
throw new UnsupportedOperationException("STUB!");
116+
}
117+
118+
private KeymasterArgument getArgumentByTag(int tag) {
119+
throw new UnsupportedOperationException("STUB!");
120+
}
121+
122+
public boolean containsTag(int tag) {
123+
throw new UnsupportedOperationException("STUB!");
124+
}
125+
126+
public int size() {
127+
throw new UnsupportedOperationException("STUB!");
128+
}
129+
130+
@Override
131+
public void writeToParcel(Parcel out, int flags) {
132+
throw new UnsupportedOperationException("STUB!");
133+
}
134+
135+
public void readFromParcel(Parcel in) {
136+
throw new UnsupportedOperationException("STUB!");
137+
}
138+
139+
@Override
140+
public int describeContents() {
141+
throw new UnsupportedOperationException("STUB!");
142+
}
143+
144+
public static BigInteger toUint64(long value) {
145+
throw new UnsupportedOperationException("STUB!");
146+
}
147+
}

0 commit comments

Comments
 (0)