support generate key

This commit is contained in:
5ec1cff
2024-07-16 09:15:08 +08:00
parent 812d2911b2
commit 994d531304
21 changed files with 1047 additions and 71 deletions

View File

@@ -26,3 +26,8 @@
-assumenosideeffects class io.github.a13e300.tricky_store.Logger {
public static void d(java.lang.String);
}
# keep these or bouncycastle will not work
-keep class org.bouncycastle.jcajce.provider.** { *; }
-keep class org.bouncycastle.jce.provider.** { *; }
-dontwarn javax.naming.**

View File

@@ -1,11 +1,14 @@
package io.github.a13e300.tricky_store
import android.content.pm.IPackageManager
import android.os.FileObserver
import android.os.ServiceManager
import io.github.a13e300.tricky_store.keystore.CertHack
import java.io.File
object Config {
val targetPackages = mutableSetOf<String>()
private val hackPackages = mutableSetOf<String>()
private val generatePackages = mutableSetOf<String>()
private val DEFAULT_TARGET_PACKAGES = listOf(
"com.google.android.gms",
"icu.nullptr.nativetest",
@@ -14,11 +17,16 @@ object Config {
)
private fun updateTargetPackages(f: File?) = runCatching {
targetPackages.clear()
f?.readLines()?.mapNotNullTo(targetPackages) {
if (it.isNotBlank() && !it.startsWith("#")) it else null
hackPackages.clear()
generatePackages.clear()
f?.readLines()?.forEach {
if (it.isNotBlank() && !it.startsWith("#")) {
val n = it.trim()
if (n.endsWith("!")) generatePackages.add(n.removeSuffix("!").trim())
else hackPackages.add(n)
}
}
Logger.i("update target packages: $targetPackages")
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages")
}.onFailure {
Logger.e("failed to update target files", it)
}
@@ -65,4 +73,25 @@ object Config {
}
ConfigObserver.startWatching()
}
private var iPm: IPackageManager? = null
fun getPm(): IPackageManager? {
if (iPm == null) {
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
}
return iPm
}
fun needHack(callingUid: Int) = kotlin.runCatching {
if (hackPackages.isEmpty()) return false
val ps = getPm()?.getPackagesForUid(callingUid)
ps?.any { it in hackPackages }
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
fun needGenerate(callingUid: Int) = kotlin.runCatching {
if (generatePackages.isEmpty()) return false
val ps = getPm()?.getPackagesForUid(callingUid)
ps?.any { it in generatePackages }
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
}

View File

@@ -1,11 +1,12 @@
package io.github.a13e300.tricky_store
import android.annotation.SuppressLint
import android.content.pm.IPackageManager
import android.hardware.security.keymint.SecurityLevel
import android.os.IBinder
import android.os.Parcel
import android.os.ServiceManager
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import io.github.a13e300.tricky_store.binder.BinderInterceptor
import io.github.a13e300.tricky_store.keystore.CertHack
@@ -13,17 +14,14 @@ import io.github.a13e300.tricky_store.keystore.Utils
import kotlin.system.exitProcess
@SuppressLint("BlockedPrivateApi")
object KeystoreInterceptor : BinderInterceptor() {
private val targetTransaction = IKeystoreService.Stub::class.java.getDeclaredField("TRANSACTION_getKeyEntry").apply { isAccessible = true }.getInt(null) // 2
object KeystoreInterceptor : BinderInterceptor() {
private val getKeyEntryTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry") // 2
private var iPm: IPackageManager? = null
private lateinit var keystore: IBinder
private fun getPm(): IPackageManager? {
if (iPm == null) {
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
}
return iPm
}
private var teeInterceptor: SecurityLevelInterceptor? = null
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
override fun onPreTransact(
target: IBinder,
@@ -33,12 +31,26 @@ object KeystoreInterceptor : BinderInterceptor() {
callingPid: Int,
data: Parcel
): Result {
if (code == targetTransaction && CertHack.canHack() && Config.targetPackages.isNotEmpty()) {
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
kotlin.runCatching {
val ps = getPm()?.getPackagesForUid(callingUid)
if (ps?.any { it in Config.targetPackages } == true) return Continue
}.onFailure { Logger.e("failed to get packages", it) }
if (code == getKeyEntryTransaction) {
if (CertHack.canHack()) {
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
if (Config.needGenerate(callingUid))
kotlin.runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor =
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
val response =
SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
?: return@runCatching
Logger.i("use generated key $callingUid ${descriptor.alias}")
val p = Parcel.obtain()
p.writeNoException()
p.writeTypedObject(response, 0)
return OverrideReply(0, p)
}
else if (Config.needHack(callingUid)) return Continue
return Skip
}
}
return Skip
}
@@ -53,7 +65,7 @@ object KeystoreInterceptor : BinderInterceptor() {
reply: Parcel?,
resultCode: Int
): Result {
if (code != targetTransaction || reply == null) return Skip
if (target != keystore || code != getKeyEntryTransaction || reply == null) return Skip
val p = Parcel.obtain()
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
try {
@@ -61,7 +73,7 @@ object KeystoreInterceptor : BinderInterceptor() {
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
val chain = Utils.getCertificateChain(response)
if (chain != null) {
val newChain = CertHack.engineGetCertificateChain(chain)
val newChain = CertHack.hackCertificateChain(chain)
Utils.putCertificateChain(response, newChain)
p.writeNoException()
p.writeTypedObject(response, 0)
@@ -98,8 +110,31 @@ object KeystoreInterceptor : BinderInterceptor() {
}
return false
}
val ks = IKeystoreService.Stub.asInterface(b)
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
.getOrNull()
val strongBox =
kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }.getOrNull()
keystore = b
Logger.i("register for Keystore $keystore!")
registerBinderInterceptor(bd, b, this)
b.linkToDeath(Killer, 0)
keystore.linkToDeath(Killer, 0)
if (tee != null) {
Logger.i("register for TEE SecurityLevel $tee!")
val interceptor = SecurityLevelInterceptor(tee)
registerBinderInterceptor(bd, tee.asBinder(), interceptor)
teeInterceptor = interceptor
} else {
Logger.i("no TEE SecurityLevel found!")
}
if (strongBox != null) {
Logger.i("register for StrongBox SecurityLevel $tee!")
val interceptor = SecurityLevelInterceptor(strongBox)
registerBinderInterceptor(bd, strongBox.asBinder(), interceptor)
strongBoxInterceptor = interceptor
} else {
Logger.i("no StrongBox SecurityLevel found!")
}
return true
}

View File

@@ -0,0 +1,141 @@
package io.github.a13e300.tricky_store
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.SecurityLevel
import android.hardware.security.keymint.Tag
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.Authorization
import android.system.keystore2.IKeystoreSecurityLevel
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata
import io.github.a13e300.tricky_store.binder.BinderInterceptor
import io.github.a13e300.tricky_store.keystore.CertHack
import io.github.a13e300.tricky_store.keystore.CertHack.KeyGenParameters
import io.github.a13e300.tricky_store.keystore.Utils
import java.security.KeyPair
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
class SecurityLevelInterceptor(private val original: IKeystoreSecurityLevel) : BinderInterceptor() {
companion object {
private val generateKeyTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
private val keys = ConcurrentHashMap<Key, Info>()
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
keys[Key(uid, alias)]?.response
}
data class Key(val uid: Int, val alias: String)
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
override fun onPreTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel
): Result {
if (code == generateKeyTransaction && Config.needGenerate(callingUid)) {
Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
kotlin.runCatching {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val params = data.createTypedArray(KeyParameter.CREATOR)!!
// val aFlags = data.readInt()
// val entropy = data.createByteArray()
val kgp = KeyGenParameters(params)
if (kgp.attestationChallenge != null) {
if (attestationKeyDescriptor != null) {
Logger.e("warn: attestation key not supported now")
} else {
val pair = CertHack.generateKeyPair(callingUid, keyDescriptor, kgp)
?: return@runCatching
val response = buildResponse(pair.second, kgp, keyDescriptor)
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
val p = Parcel.obtain()
p.writeNoException()
p.writeTypedObject(response.metadata, 0)
return OverrideReply(0, p)
}
}
}.onFailure {
Logger.e("parse key gen request", it)
}
}
return Skip
}
private fun buildResponse(
chain: List<Certificate>,
params: KeyGenParameters,
descriptor: KeyDescriptor
): KeyEntryResponse {
val response = KeyEntryResponse()
val metadata = KeyMetadata()
Utils.putCertificateChain(metadata, chain.toTypedArray<Certificate>())
val d = KeyDescriptor()
d.domain = descriptor.domain
d.nspace = descriptor.nspace
metadata.key = d
val authorizations = ArrayList<Authorization>()
var a: Authorization
for (i in params.purpose) {
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.PURPOSE
a.keyParameter.value = KeyParameterValue.keyPurpose(i)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
}
for (i in params.digest) {
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.DIGEST
a.keyParameter.value = KeyParameterValue.digest(i)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
}
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.ALGORITHM
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.KEY_SIZE
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.EC_CURVE
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
a.keyParameter.value = KeyParameterValue.boolValue(true) // TODO: copy
a.securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT
authorizations.add(a)
// TODO: ORIGIN
//OS_VERSION
//OS_PATCHLEVEL
//VENDOR_PATCHLEVEL
//BOOT_PATCHLEVEL
//CREATION_DATETIME
//USER_ID
metadata.authorizations = authorizations.toTypedArray<Authorization>()
response.metadata = metadata
response.iSecurityLevel = original
return response
}
}

View File

@@ -1,22 +1,38 @@
package io.github.a13e300.tricky_store.keystore;
import android.content.pm.PackageManager;
import android.hardware.security.keymint.Algorithm;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.Tag;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyDescriptor;
import android.util.Pair;
import androidx.annotation.Nullable;
import org.bouncycastle.asn1.ASN1Boolean;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
@@ -25,25 +41,46 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.io.pem.PemReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import javax.security.auth.x500.X500Principal;
import io.github.a13e300.tricky_store.Config;
import io.github.a13e300.tricky_store.Logger;
import io.github.a13e300.tricky_store.UtilKt;
public final class CertHack {
private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17");
record KeyBox(PEMKeyPair privateKey, List<Certificate> certificates) {}
private static final int ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0;
private static final int ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1;
private static final Map<String, KeyBox> keyboxes = new HashMap<>();
private static final int ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0;
private static final CertificateFactory certificateFactory;
@@ -55,11 +92,31 @@ public final class CertHack {
throw new RuntimeException(t);
}
}
private static final int ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1;
public static boolean canHack() {
return !keyboxes.isEmpty();
}
private static PEMKeyPair parseKeyPair(String key) throws Throwable {
try (PEMParser parser = new PEMParser(new StringReader(key))) {
return (PEMKeyPair) parser.readObject();
}
}
private static Certificate parseCert(String cert) throws Throwable {
try (PemReader reader = new PemReader(new StringReader(cert))) {
return certificateFactory.generateCertificate(new ByteArrayInputStream(reader.readPemObject().getContent()));
}
}
private static byte[] getByteArrayFromAsn1(ASN1Encodable asn1Encodable) throws CertificateParsingException {
if (!(asn1Encodable instanceof DEROctetString derOctectString)) {
throw new CertificateParsingException("Expected DEROctetString");
}
return derOctectString.getOctets();
}
public static void readFromXml(String data) {
keyboxes.clear();
if (data == null) {
@@ -92,7 +149,9 @@ public final class CertHack {
} else {
algo = KeyProperties.KEY_ALGORITHM_RSA;
}
keyboxes.put(algo, new KeyBox(parseKeyPair(privateKey), certificateChain));
var pemKp = parseKeyPair(privateKey);
var kp = new JcaPEMKeyConverter().getKeyPair(pemKp);
keyboxes.put(algo, new KeyBox(pemKp, kp, certificateChain));
}
Logger.i("update " + numberOfKeyboxes + " keyboxes");
} catch (Throwable t) {
@@ -100,26 +159,7 @@ public final class CertHack {
}
}
private static PEMKeyPair parseKeyPair(String key) throws Throwable {
try (PEMParser parser = new PEMParser(new StringReader(key))) {
return (PEMKeyPair) parser.readObject();
}
}
private static Certificate parseCert(String cert) throws Throwable {
try (PemReader reader = new PemReader(new StringReader(cert))) {
return certificateFactory.generateCertificate(new ByteArrayInputStream(reader.readPemObject().getContent()));
}
}
private static byte[] getByteArrayFromAsn1(ASN1Encodable asn1Encodable) throws CertificateParsingException {
if (!(asn1Encodable instanceof DEROctetString derOctectString)) {
throw new CertificateParsingException("Expected DEROctetString");
}
return derOctectString.getOctets();
}
public static Certificate[] engineGetCertificateChain(Certificate[] caList) {
public static Certificate[] hackCertificateChain(Certificate[] caList) {
if (caList == null) throw new UnsupportedOperationException("caList is null!");
try {
X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded()));
@@ -158,14 +198,10 @@ public final class CertHack {
holder.getNotBefore(),
holder.getNotAfter(),
holder.getSubject(),
k.privateKey.getPublicKeyInfo()
k.pemKeyPair.getPublicKeyInfo()
);
signer = new JcaContentSignerBuilder(leaf.getSigAlgName())
.build(
new JcaPEMKeyConverter()
.getPrivateKey(k.privateKey.getPrivateKeyInfo()
)
);
.build(k.keyPair.getPrivate());
byte[] verifiedBootKey = null;
byte[] verifiedBootHash = null;
@@ -221,4 +257,286 @@ public final class CertHack {
}
return caList;
}
public static Pair<KeyPair, List<Certificate>> generateKeyPair(int uid, KeyDescriptor descriptor, KeyGenParameters params) {
Logger.i("Requested KeyPair with alias: " + descriptor.alias);
KeyPair rootKP;
X500Name issuer;
int size = params.keySize;
KeyPair kp = null;
KeyBox keyBox = null;
try {
var algo = params.algorithm;
if (algo == Algorithm.EC) {
Logger.d("GENERATING EC KEYPAIR OF SIZE " + size);
kp = buildECKeyPair(params);
keyBox = keyboxes.get(KeyProperties.KEY_ALGORITHM_EC);
} else if (algo == Algorithm.RSA) {
Logger.d("GENERATING RSA KEYPAIR OF SIZE " + size);
kp = buildRSAKeyPair(params);
keyBox = keyboxes.get(KeyProperties.KEY_ALGORITHM_RSA);
}
if (keyBox == null) {
Logger.e("UNSUPPORTED ALGORITHM: " + algo);
return null;
}
rootKP = keyBox.keyPair;
issuer = new X509CertificateHolder(
keyBox.certificates.get(0).getEncoded()
).getSubject();
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer,
params.certificateSerial,
params.certificateNotBefore,
params.certificateNotAfter,
params.certificateSubject,
kp.getPublic()
);
KeyUsage keyUsage = new KeyUsage(KeyUsage.keyCertSign);
certBuilder.addExtension(Extension.keyUsage, true, keyUsage);
certBuilder.addExtension(createExtension(params, uid));
ContentSigner contentSigner;
if (algo == Algorithm.EC) {
contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(rootKP.getPrivate());
} else {
contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(rootKP.getPrivate());
}
X509CertificateHolder certHolder = certBuilder.build(contentSigner);
var leaf = new JcaX509CertificateConverter().getCertificate(certHolder);
List<Certificate> chain = new ArrayList<>(keyBox.certificates);
chain.add(0, leaf);
Logger.d("Successfully generated X500 Cert for alias: " + descriptor.alias);
return new Pair<>(kp, chain);
} catch (Throwable t) {
Logger.e("", t);
}
return null;
}
private static KeyPair buildECKeyPair(KeyGenParameters params) throws Exception {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.addProvider(new BouncyCastleProvider());
ECGenParameterSpec spec = new ECGenParameterSpec(params.ecCurveName);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
kpg.initialize(spec);
return kpg.generateKeyPair();
}
private static KeyPair buildRSAKeyPair(KeyGenParameters params) throws Exception {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.addProvider(new BouncyCastleProvider());
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(
params.keySize, params.rsaPublicExponent);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
kpg.initialize(spec);
return kpg.generateKeyPair();
}
private static ASN1Encodable[] fromIntList(List<Integer> list) {
ASN1Encodable[] result = new ASN1Encodable[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = new ASN1Integer(list.get(i));
}
return result;
}
private static Extension createExtension(KeyGenParameters params, int uid) {
try {
SecureRandom random = new SecureRandom();
byte[] key = new byte[32];
byte[] hash = UtilKt.getBootHashFromProp();
random.nextBytes(key);
if (hash == null || hash.length != 32) {
hash = new byte[32];
random.nextBytes(hash);
}
ASN1Encodable[] rootOfTrustEncodables = {new DEROctetString(key), ASN1Boolean.TRUE,
new ASN1Enumerated(0), new DEROctetString(hash)};
ASN1Sequence rootOfTrustSeq = new DERSequence(rootOfTrustEncodables);
var Apurpose = new DERSet(fromIntList(params.purpose));
var Aalgorithm = new ASN1Integer(params.algorithm);
var AkeySize = new ASN1Integer(params.keySize);
var Adigest = new DERSet(fromIntList(params.digest));
var AecCurve = new ASN1Integer(params.ecCurve);
var AnoAuthRequired = DERNull.INSTANCE;
// To be loaded
var AosVersion = new ASN1Integer(UtilKt.getOsVersion());
var AosPatchLevel = new ASN1Integer(UtilKt.getPatchLevel());
// TODO hex3l: add applicationID to attestation
var AapplicationID = createApplicationId(uid);
var AbootPatchlevel = new ASN1Integer(UtilKt.getPatchLevelLong());
var AvendorPatchLevel = new ASN1Integer(UtilKt.getPatchLevelLong());
var AcreationDateTime = new ASN1Integer(System.currentTimeMillis());
var Aorigin = new ASN1Integer(0);
var purpose = new DERTaggedObject(true, 1, Apurpose);
var algorithm = new DERTaggedObject(true, 2, Aalgorithm);
var keySize = new DERTaggedObject(true, 3, AkeySize);
var digest = new DERTaggedObject(true, 5, Adigest);
var ecCurve = new DERTaggedObject(true, 10, AecCurve);
var noAuthRequired = new DERTaggedObject(true, 503, AnoAuthRequired);
var creationDateTime = new DERTaggedObject(true, 701, AcreationDateTime);
var origin = new DERTaggedObject(true, 702, Aorigin);
var rootOfTrust = new DERTaggedObject(true, 704, rootOfTrustSeq);
var osVersion = new DERTaggedObject(true, 705, AosVersion);
var osPatchLevel = new DERTaggedObject(true, 706, AosPatchLevel);
var applicationID = new DERTaggedObject(true, 709, AapplicationID);
var vendorPatchLevel = new DERTaggedObject(true, 718, AvendorPatchLevel);
var bootPatchLevel = new DERTaggedObject(true, 719, AbootPatchlevel);
ASN1Encodable[] teeEnforcedEncodables = {purpose, algorithm, keySize, digest, ecCurve,
noAuthRequired, creationDateTime, origin, rootOfTrust, osVersion, osPatchLevel, applicationID, vendorPatchLevel, bootPatchLevel};
ASN1OctetString keyDescriptionOctetStr = getAsn1OctetString(teeEnforcedEncodables, params);
return new Extension(new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17"), false, keyDescriptionOctetStr);
} catch (Throwable t) {
Logger.e("", t);
}
return null;
}
private static ASN1OctetString getAsn1OctetString(ASN1Encodable[] teeEnforcedEncodables, KeyGenParameters params) throws IOException {
ASN1Integer attestationVersion = new ASN1Integer(100);
ASN1Enumerated attestationSecurityLevel = new ASN1Enumerated(1);
ASN1Integer keymasterVersion = new ASN1Integer(100);
ASN1Enumerated keymasterSecurityLevel = new ASN1Enumerated(1);
ASN1OctetString attestationChallenge = new DEROctetString(params.attestationChallenge);
ASN1OctetString uniqueId = new DEROctetString("".getBytes());
ASN1Sequence softwareEnforced = new DERSequence();
ASN1Sequence teeEnforced = new DERSequence(teeEnforcedEncodables);
ASN1Encodable[] keyDescriptionEncodables = {attestationVersion, attestationSecurityLevel, keymasterVersion,
keymasterSecurityLevel, attestationChallenge, uniqueId, softwareEnforced, teeEnforced};
ASN1Sequence keyDescriptionHackSeq = new DERSequence(keyDescriptionEncodables);
return new DEROctetString(keyDescriptionHackSeq);
}
private static DEROctetString createApplicationId(int uid) throws Throwable {
var pm = Config.INSTANCE.getPm();
if (pm == null) {
throw new IllegalStateException("createApplicationId: pm not found!");
}
var packages = pm.getPackagesForUid(uid);
var size = packages.length;
ASN1Encodable[] packageInfoAA = new ASN1Encodable[size];
Set<Digest> signatures = new HashSet<>();
var dg = MessageDigest.getInstance("SHA-256");
for (int i = 0; i < size; i++) {
var name = packages[i];
var info = pm.getPackageInfo(name, PackageManager.GET_SIGNATURES, uid / 100000);
ASN1Encodable[] arr = new ASN1Encodable[2];
arr[ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX] =
new DEROctetString(packages[i].getBytes(StandardCharsets.UTF_8));
arr[ATTESTATION_PACKAGE_INFO_VERSION_INDEX] = new ASN1Integer(info.getLongVersionCode());
packageInfoAA[i] = new DERSequence(arr);
for (var s : info.signatures) {
signatures.add(new Digest(dg.digest(s.toByteArray())));
}
}
ASN1Encodable[] signaturesAA = new ASN1Encodable[signatures.size()];
var i = 0;
for (var d : signatures) {
signaturesAA[i] = new DEROctetString(d.digest);
i++;
}
ASN1Encodable[] applicationIdAA = new ASN1Encodable[2];
applicationIdAA[ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX] =
new DERSet(packageInfoAA);
applicationIdAA[ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX] =
new DERSet(signaturesAA);
return new DEROctetString(new DERSequence(applicationIdAA).getEncoded());
}
record Digest(byte[] digest) {
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof Digest d)
return Arrays.equals(digest, d.digest);
return false;
}
@Override
public int hashCode() {
return Arrays.hashCode(digest);
}
}
record KeyBox(PEMKeyPair pemKeyPair, KeyPair keyPair, List<Certificate> certificates) {
}
public static class KeyGenParameters {
public int keySize;
public int algorithm;
public BigInteger certificateSerial;
public Date certificateNotBefore;
public Date certificateNotAfter;
public X500Name certificateSubject;
public BigInteger rsaPublicExponent;
public int ecCurve;
public String ecCurveName;
public List<Integer> purpose = new ArrayList<>();
public List<Integer> digest = new ArrayList<>();
public byte[] attestationChallenge;
public KeyGenParameters(KeyParameter[] params) {
for (var kp : params) {
var p = kp.value;
switch (kp.tag) {
case Tag.KEY_SIZE -> keySize = p.getInteger();
case Tag.ALGORITHM -> algorithm = p.getAlgorithm();
case Tag.CERTIFICATE_SERIAL -> certificateSerial = new BigInteger(p.getBlob());
case Tag.CERTIFICATE_NOT_BEFORE ->
certificateNotBefore = new Date(p.getDateTime());
case Tag.CERTIFICATE_NOT_AFTER ->
certificateNotAfter = new Date(p.getDateTime());
case Tag.CERTIFICATE_SUBJECT ->
certificateSubject = new X500Name(new X500Principal(p.getBlob()).getName());
case Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = new BigInteger(p.getBlob());
case Tag.EC_CURVE -> {
ecCurve = p.getEcCurve();
ecCurveName = getEcCurveName(ecCurve);
}
case Tag.PURPOSE -> {
purpose.add(p.getKeyPurpose());
}
case Tag.DIGEST -> {
digest.add(p.getDigest());
}
case Tag.ATTESTATION_CHALLENGE -> attestationChallenge = p.getBlob();
}
}
}
private static String getEcCurveName(int curve) {
String res;
switch (curve) {
case EcCurve.CURVE_25519 -> res = "CURVE_25519";
case EcCurve.P_224 -> res = "secp224r1";
case EcCurve.P_256 -> res = "secp256r1";
case EcCurve.P_384 -> res = "secp384r1";
case EcCurve.P_521 -> res = "secp521r1";
default -> throw new IllegalArgumentException("unknown curve");
}
return res;
}
}
}

View File

@@ -1,6 +1,7 @@
package io.github.a13e300.tricky_store.keystore;
import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.KeyMetadata;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -58,12 +59,16 @@ public class Utils {
}
public static void putCertificateChain(KeyEntryResponse response, Certificate[] chain) throws Throwable {
putCertificateChain(response.metadata, chain);
}
public static void putCertificateChain(KeyMetadata metadata, Certificate[] chain) throws Throwable {
if (chain == null || chain.length == 0) return;
response.metadata.certificate = chain[0].getEncoded();
metadata.certificate = chain[0].getEncoded();
var output = new ByteArrayOutputStream();
for (int i = 1; i < chain.length; i++) {
output.write(chain[i].getEncoded());
}
response.metadata.certificateChain = output.toByteArray();
metadata.certificateChain = output.toByteArray();
}
}

View File

@@ -0,0 +1,40 @@
package io.github.a13e300.tricky_store
import android.os.Build
import android.os.SystemProperties
fun getTransactCode(clazz: Class<*>, method: String) =
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
.getInt(null) // 2
@OptIn(ExperimentalStdlibApi::class)
val bootHashFromProp by lazy {
val b = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return@lazy null
if (b.length != 64) return@lazy null
b.hexToByteArray()
}
val patchLevel by lazy {
Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
}
val patchLevelLong by lazy {
Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
}
// FIXME
val osVersion by lazy {
when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> 140000
Build.VERSION_CODES.TIRAMISU -> 130000
Build.VERSION_CODES.S_V2 -> 120100
Build.VERSION_CODES.S -> 120000
else -> 0
}
}
fun String.convertPatchLevel(long: Boolean) = kotlin.runCatching {
val l = split("-")
if (long) l[0].toInt() * 10000 + l[1].toInt() * 100 + l[2].toInt()
else l[0].toInt() * 100 + l[1].toInt()
}.onFailure { Logger.e("invalid patch level $this !", it) }.getOrDefault(202404)

View File

@@ -5,6 +5,8 @@ import android.os.IBinder;
public interface IPackageManager {
String[] getPackagesForUid(int uid);
PackageInfo getPackageInfo(String packageName, long flags, int userId);
class Stub {
public static IPackageManager asInterface(IBinder binder) {
throw new RuntimeException("");

View File

@@ -0,0 +1,9 @@
package android.hardware.security.keymint;
public @interface Algorithm {
int AES = 32;
int EC = 3;
int HMAC = 128;
int RSA = 1;
int TRIPLE_DES = 33;
}

View File

@@ -0,0 +1,10 @@
package android.hardware.security.keymint;
/* loaded from: classes2.dex */
public @interface EcCurve {
public static final int CURVE_25519 = 4;
public static final int P_224 = 0;
public static final int P_256 = 1;
public static final int P_384 = 2;
public static final int P_521 = 3;
}

View File

@@ -0,0 +1,32 @@
package android.hardware.security.keymint;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class KeyParameter implements Parcelable {
public static final Creator<KeyParameter> CREATOR = new Creator<>() {
@Override
public KeyParameter createFromParcel(Parcel in) {
throw new RuntimeException();
}
@Override
public KeyParameter[] newArray(int size) {
throw new RuntimeException();
}
};
public int tag = 0;
public KeyParameterValue value;
@Override
public int describeContents() {
throw new RuntimeException();
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
throw new RuntimeException();
}
}

View File

@@ -0,0 +1,239 @@
package android.hardware.security.keymint;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
/* loaded from: classes2.dex */
public final class KeyParameterValue implements Parcelable {
public static final int algorithm = 1;
public static final int blob = 14;
public static final int blockMode = 2;
public static final int boolValue = 10;
public static final int dateTime = 13;
public static final int digest = 4;
public static final int ecCurve = 5;
public static final int hardwareAuthenticatorType = 8;
public static final int integer = 11;
public static final int invalid = 0;
public static final int keyPurpose = 7;
public static final int longInteger = 12;
public static final int origin = 6;
public static final int paddingMode = 3;
public static final int securityLevel = 9;
public static final Creator<KeyParameterValue> CREATOR = new Creator<KeyParameterValue>() {
@Override
public KeyParameterValue createFromParcel(Parcel in) {
throw new RuntimeException();
}
@Override
public KeyParameterValue[] newArray(int size) {
throw new RuntimeException();
}
};
public KeyParameterValue() {
throw new RuntimeException();
}
protected KeyParameterValue(Parcel in) {
throw new RuntimeException();
}
public static KeyParameterValue invalid(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue algorithm(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue blockMode(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue paddingMode(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue digest(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue ecCurve(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue origin(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue keyPurpose(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue hardwareAuthenticatorType(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue securityLevel(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue boolValue(boolean _value) {
throw new RuntimeException();
}
public static KeyParameterValue integer(int _value) {
throw new RuntimeException();
}
public static KeyParameterValue longInteger(long _value) {
throw new RuntimeException();
}
public static KeyParameterValue dateTime(long _value) {
throw new RuntimeException();
}
public static KeyParameterValue blob(byte[] _value) {
throw new RuntimeException();
}
public int getTag() {
throw new RuntimeException();
}
public int getInvalid() {
throw new RuntimeException();
}
public void setInvalid(int _value) {
throw new RuntimeException();
}
public int getAlgorithm() {
throw new RuntimeException();
}
public void setAlgorithm(int _value) {
throw new RuntimeException();
}
public int getBlockMode() {
throw new RuntimeException();
}
public void setBlockMode(int _value) {
throw new RuntimeException();
}
public int getPaddingMode() {
throw new RuntimeException();
}
public void setPaddingMode(int _value) {
throw new RuntimeException();
}
public int getDigest() {
throw new RuntimeException();
}
public void setDigest(int _value) {
throw new RuntimeException();
}
public int getEcCurve() {
throw new RuntimeException();
}
public void setEcCurve(int _value) {
throw new RuntimeException();
}
public int getOrigin() {
throw new RuntimeException();
}
public void setOrigin(int _value) {
throw new RuntimeException();
}
public int getKeyPurpose() {
throw new RuntimeException();
}
public void setKeyPurpose(int _value) {
throw new RuntimeException();
}
public int getHardwareAuthenticatorType() {
throw new RuntimeException();
}
public void setHardwareAuthenticatorType(int _value) {
throw new RuntimeException();
}
public int getSecurityLevel() {
throw new RuntimeException();
}
public void setSecurityLevel(int _value) {
throw new RuntimeException();
}
public boolean getBoolValue() {
throw new RuntimeException();
}
public void setBoolValue(boolean _value) {
throw new RuntimeException();
}
public int getInteger() {
throw new RuntimeException();
}
public void setInteger(int _value) {
throw new RuntimeException();
}
public long getLongInteger() {
throw new RuntimeException();
}
public void setLongInteger(long _value) {
throw new RuntimeException();
}
public long getDateTime() {
throw new RuntimeException();
}
public void setDateTime(long _value) {
throw new RuntimeException();
}
public byte[] getBlob() {
throw new RuntimeException();
}
public void setBlob(byte[] _value) {
throw new RuntimeException();
}
@Override
public int describeContents() {
throw new RuntimeException();
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
throw new RuntimeException();
}
}

View File

@@ -0,0 +1,11 @@
package android.hardware.security.keymint;
public @interface KeyPurpose {
int AGREE_KEY = 6;
int ATTEST_KEY = 7;
int DECRYPT = 1;
int ENCRYPT = 0;
int SIGN = 2;
int VERIFY = 3;
int WRAP_KEY = 5;
}

View File

@@ -0,0 +1,8 @@
package android.hardware.security.keymint;
public @interface SecurityLevel {
int KEYSTORE = 100;
int SOFTWARE = 0;
int STRONGBOX = 2;
int TRUSTED_ENVIRONMENT = 1;
}

View File

@@ -0,0 +1,70 @@
package android.hardware.security.keymint;
public @interface Tag {
int ACTIVE_DATETIME = 1610613136;
int ALGORITHM = 268435458;
int ALLOW_WHILE_ON_BODY = 1879048698;
int APPLICATION_DATA = -1879047492;
int APPLICATION_ID = -1879047591;
int ASSOCIATED_DATA = -1879047192;
int ATTESTATION_APPLICATION_ID = -1879047483;
int ATTESTATION_CHALLENGE = -1879047484;
int ATTESTATION_ID_BRAND = -1879047482;
int ATTESTATION_ID_DEVICE = -1879047481;
int ATTESTATION_ID_IMEI = -1879047478;
int ATTESTATION_ID_MANUFACTURER = -1879047476;
int ATTESTATION_ID_MEID = -1879047477;
int ATTESTATION_ID_MODEL = -1879047475;
int ATTESTATION_ID_PRODUCT = -1879047480;
int ATTESTATION_ID_SECOND_IMEI = -1879047469;
int ATTESTATION_ID_SERIAL = -1879047479;
int AUTH_TIMEOUT = 805306873;
int BLOCK_MODE = 536870916;
int BOOTLOADER_ONLY = 1879048494;
int BOOT_PATCHLEVEL = 805307087;
int CALLER_NONCE = 1879048199;
int CERTIFICATE_NOT_AFTER = 1610613745;
int CERTIFICATE_NOT_BEFORE = 1610613744;
int CERTIFICATE_SERIAL = -2147482642;
int CERTIFICATE_SUBJECT = -1879047185;
int CONFIRMATION_TOKEN = -1879047187;
int CREATION_DATETIME = 1610613437;
int DEVICE_UNIQUE_ATTESTATION = 1879048912;
int DIGEST = 536870917;
int EARLY_BOOT_ONLY = 1879048497;
int EC_CURVE = 268435466;
int HARDWARE_TYPE = 268435760;
int IDENTITY_CREDENTIAL_KEY = 1879048913;
int INCLUDE_UNIQUE_ID = 1879048394;
int INVALID = 0;
int KEY_SIZE = 805306371;
int MAC_LENGTH = 805307371;
int MAX_BOOT_LEVEL = 805307378;
int MAX_USES_PER_BOOT = 805306772;
int MIN_MAC_LENGTH = 805306376;
int MIN_SECONDS_BETWEEN_OPS = 805306771;
int NONCE = -1879047191;
int NO_AUTH_REQUIRED = 1879048695;
int ORIGIN = 268436158;
int ORIGINATION_EXPIRE_DATETIME = 1610613137;
int OS_PATCHLEVEL = 805307074;
int OS_VERSION = 805307073;
int PADDING = 536870918;
int PURPOSE = 536870913;
int RESET_SINCE_ID_ROTATION = 1879049196;
int ROLLBACK_RESISTANCE = 1879048495;
int ROOT_OF_TRUST = -1879047488;
int RSA_OAEP_MGF_DIGEST = 536871115;
int RSA_PUBLIC_EXPONENT = 1342177480;
int STORAGE_KEY = 1879048914;
int TRUSTED_CONFIRMATION_REQUIRED = 1879048700;
int TRUSTED_USER_PRESENCE_REQUIRED = 1879048699;
int UNIQUE_ID = -1879047485;
int UNLOCKED_DEVICE_REQUIRED = 1879048701;
int USAGE_COUNT_LIMIT = 805306773;
int USAGE_EXPIRE_DATETIME = 1610613138;
int USER_AUTH_TYPE = 268435960;
int USER_ID = 805306869;
int USER_SECURE_ID = -1610612234;
int VENDOR_PATCHLEVEL = 805307086;
}

View File

@@ -0,0 +1,8 @@
package android.system.keystore2;
import android.hardware.security.keymint.KeyParameter;
public class Authorization {
public KeyParameter keyParameter;
public int securityLevel = 0;
}

View File

@@ -0,0 +1,20 @@
package android.system.keystore2;
import android.hardware.security.keymint.KeyParameter;
import android.os.IBinder;
import android.os.IInterface;
import androidx.annotation.Nullable;
public interface IKeystoreSecurityLevel extends IInterface {
String DESCRIPTOR = "android.system.keystore2.IKeystoreSecurityLevel";
KeyMetadata generateKey(KeyDescriptor key, @Nullable KeyDescriptor attestationKey,
KeyParameter[] params, int flags, byte[] entropy);
class Stub {
public static IKeystoreSecurityLevel asInterface(IBinder b) {
throw new RuntimeException();
}
}
}

View File

@@ -1,9 +1,15 @@
package android.system.keystore2;
import android.os.IBinder;
public interface IKeystoreService {
String DESCRIPTOR = "android.system.keystore2.IKeystoreService";
class Stub {
IKeystoreSecurityLevel getSecurityLevel(int securityLevel);
class Stub {
public static IKeystoreService asInterface(IBinder b) {
throw new RuntimeException("");
}
}
}

View File

@@ -11,19 +11,15 @@ public class KeyDescriptor implements Parcelable {
public int domain = 0;
public long nspace = 0;
protected KeyDescriptor(Parcel in) {
throw new RuntimeException("");
}
public static final Creator<KeyDescriptor> CREATOR = new Creator<KeyDescriptor>() {
@Override
public KeyDescriptor createFromParcel(Parcel in) {
return new KeyDescriptor(in);
throw new RuntimeException();
}
@Override
public KeyDescriptor[] newArray(int size) {
return new KeyDescriptor[size];
throw new RuntimeException();
}
};

View File

@@ -6,13 +6,9 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
public class KeyEntryResponse implements Parcelable {
// public IKeystoreSecurityLevel iSecurityLevel;
public IKeystoreSecurityLevel iSecurityLevel;
public KeyMetadata metadata;
protected KeyEntryResponse(Parcel in) {
throw new RuntimeException("");
}
public static final Creator<KeyEntryResponse> CREATOR = new Creator<KeyEntryResponse>() {
@Override
public KeyEntryResponse createFromParcel(Parcel in) {

View File

@@ -6,17 +6,13 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
public class KeyMetadata implements Parcelable {
// public Authorization[] authorizations;
public Authorization[] authorizations;
public byte[] certificate;
public byte[] certificateChain;
public KeyDescriptor key;
public int keySecurityLevel = 0;
public long modificationTimeMs = 0;
protected KeyMetadata(Parcel in) {
throw new RuntimeException("");
}
public static final Creator<KeyMetadata> CREATOR = new Creator<KeyMetadata>() {
@Override
public KeyMetadata createFromParcel(Parcel in) {