mirror of
https://github.com/chiteroman/TrickyStore.git
synced 2025-07-17 15:29:32 +00:00
works now
This commit is contained in:
1
service/.gitignore
vendored
Normal file
1
service/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
32
service/build.gradle.kts
Normal file
32
service/build.gradle.kts
Normal file
@@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
alias(libs.plugins.jetbrains.kotlin.android)
|
||||
alias(libs.plugins.agp.app)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.github.a13e300.tricky_store"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "io.github.a13e300.tricky_store"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":stub"))
|
||||
compileOnly(libs.annotation)
|
||||
implementation(libs.bcpkix.jdk15on)
|
||||
}
|
||||
21
service/proguard-rules.pro
vendored
Normal file
21
service/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
2
service/src/main/AndroidManifest.xml
Normal file
2
service/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
224
service/src/main/java/io/github/a13e300/tricky_store/Main.kt
Normal file
224
service/src/main/java/io/github/a13e300/tricky_store/Main.kt
Normal file
@@ -0,0 +1,224 @@
|
||||
package io.github.a13e300.tricky_store
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.IPackageManager
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.os.ServiceManager
|
||||
import android.system.keystore2.IKeystoreService
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import android.util.Log
|
||||
import io.github.a13e300.tricky_store.fwpatch.Android
|
||||
import io.github.a13e300.tricky_store.fwpatch.Utils
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
open class BinderInterceptor : Binder() {
|
||||
sealed class Result
|
||||
data object Skip : Result()
|
||||
data object Continue : Result()
|
||||
data class OverrideData(val data: Parcel) : Result()
|
||||
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
|
||||
|
||||
open fun onPreTransact(target: IBinder, code: Int, flags: Int, callingUid: Int, callingPid: Int, data: Parcel): Result = Skip
|
||||
open fun onPostTransact(target: IBinder, code: Int, flags: Int, callingUid: Int, callingPid: Int, data: Parcel, reply: Parcel?, resultCode: Int): Result = Skip
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
val result = when (code) {
|
||||
1 -> { // PRE_TRANSACT
|
||||
val target = data.readStrongBinder()
|
||||
val theCode = data.readInt()
|
||||
val theFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val sz = data.readLong()
|
||||
val theData = Parcel.obtain()
|
||||
try {
|
||||
theData.appendFrom(data, data.dataPosition(), sz.toInt())
|
||||
theData.setDataPosition(0)
|
||||
onPreTransact(target, theCode, theFlags, callingUid, callingPid, theData)
|
||||
} finally {
|
||||
theData.recycle()
|
||||
}
|
||||
}
|
||||
2 -> { // POST_TRANSACT
|
||||
val target = data.readStrongBinder()
|
||||
val theCode = data.readInt()
|
||||
val theFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val resultCode = data.readInt()
|
||||
val theData = Parcel.obtain()
|
||||
val theReply = Parcel.obtain()
|
||||
try {
|
||||
val sz = data.readLong().toInt()
|
||||
theData.appendFrom(data, data.dataPosition(), sz)
|
||||
theData.setDataPosition(0)
|
||||
data.setDataPosition(data.dataPosition() + sz)
|
||||
val sz2 = data.readLong().toInt()
|
||||
if (sz2 != 0) {
|
||||
theReply.appendFrom(data, data.dataPosition(), sz2)
|
||||
theReply.setDataPosition(0)
|
||||
}
|
||||
onPostTransact(target, theCode, theFlags, callingUid, callingPid, theData, if (sz2 == 0) null else theReply, resultCode)
|
||||
} finally {
|
||||
theData.recycle()
|
||||
theReply.recycle()
|
||||
}
|
||||
}
|
||||
else -> return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
when (result) {
|
||||
Skip -> reply!!.writeInt(1)
|
||||
Continue -> reply!!.writeInt(2)
|
||||
is OverrideReply -> {
|
||||
reply!!.writeInt(3)
|
||||
reply.writeInt(result.code)
|
||||
reply.writeLong(result.reply.dataSize().toLong())
|
||||
println("override reply code=${result.code} size=${result.reply.dataSize()}")
|
||||
reply.appendFrom(result.reply, 0, result.reply.dataSize())
|
||||
result.reply.recycle()
|
||||
}
|
||||
is OverrideData -> {
|
||||
reply!!.writeInt(4)
|
||||
reply.writeLong(result.data.dataSize().toLong())
|
||||
reply.appendFrom(result.data, 0, result.data.dataSize())
|
||||
result.data.recycle()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun getBinderBackdoor(b: IBinder): IBinder? {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
try {
|
||||
b.transact(0xdeadbeef.toInt(), data, reply, 0)
|
||||
return reply.readStrongBinder()
|
||||
} catch (ignored: Throwable) {
|
||||
return null
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun registerBinderInterceptor(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
data.writeStrongBinder(target)
|
||||
data.writeStrongBinder(interceptor)
|
||||
backdoor.transact(1, data, reply, 0)
|
||||
}
|
||||
|
||||
val targetPackages = arrayOf("com.google.android.gms", "icu.nullptr.nativetest", "io.github.vvb2060.mahoshojo", "io.github.vvb2060.keyattestation")
|
||||
|
||||
const val TAG = "TrickyStore"
|
||||
|
||||
fun logD(msg: String) {
|
||||
Log.d(TAG, msg)
|
||||
}
|
||||
|
||||
fun logE(msg: String, t: Throwable? = null) {
|
||||
if (t == null) {
|
||||
Log.e(TAG, msg)
|
||||
} else {
|
||||
Log.e(TAG, msg, t)
|
||||
}
|
||||
}
|
||||
|
||||
var iPm: IPackageManager? = null
|
||||
|
||||
fun getPm(): IPackageManager? {
|
||||
if (iPm == null) {
|
||||
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||
}
|
||||
return iPm
|
||||
}
|
||||
|
||||
@SuppressLint("BlockedPrivateApi")
|
||||
fun tryRunKeystoreInterceptor(): Boolean {
|
||||
val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false
|
||||
b.linkToDeath({
|
||||
logD("keystore exit, daemon exit")
|
||||
exitProcess(0)
|
||||
}, 0)
|
||||
val bd = getBinderBackdoor(b) ?: return true
|
||||
val targetTransaction = IKeystoreService.Stub::class.java.getDeclaredField("TRANSACTION_getKeyEntry").apply { isAccessible = true }.getInt(null) // 2
|
||||
val interceptor = object : BinderInterceptor() {
|
||||
override fun onPreTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
): Result {
|
||||
if (code == targetTransaction) {
|
||||
logD("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
|
||||
kotlin.runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid)
|
||||
if (ps?.any { it in targetPackages } == true) return Continue
|
||||
}.onFailure { logE("failed to get packages", it) }
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int
|
||||
): Result {
|
||||
if (code != targetTransaction || reply == null) return Skip
|
||||
val p = Parcel.obtain()
|
||||
logD("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||
try {
|
||||
reply.readException()
|
||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
||||
val chain = Utils.getCertificateChain(response)
|
||||
val newChain = Android.engineGetCertificateChain(chain)
|
||||
Utils.putCertificateChain(response, newChain)
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response, 0)
|
||||
return OverrideReply(0, p)
|
||||
} catch (t: Throwable) {
|
||||
logE("failed to hack certificate chain!", t)
|
||||
p.recycle()
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
registerBinderInterceptor(bd, b, interceptor)
|
||||
while (true) {
|
||||
Thread.sleep(1000000)
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
while (true) {
|
||||
Thread.sleep(1000)
|
||||
// true -> can inject, false -> service not found, loop -> running
|
||||
if (!tryRunKeystoreInterceptor()) continue
|
||||
// no binder hook, try inject
|
||||
val p = Runtime.getRuntime().exec(
|
||||
arrayOf(
|
||||
"/system/bin/sh",
|
||||
"-c",
|
||||
"exec ./inject `pidof keystore2` libtricky_store.so entry"
|
||||
)
|
||||
)
|
||||
// logD(p.inputStream.readBytes().decodeToString())
|
||||
// logD(p.errorStream.readBytes().decodeToString())
|
||||
if (p.waitFor() != 0) {
|
||||
logE("failed to inject! daemon exit")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package io.github.a13e300.tricky_store.fwpatch;
|
||||
|
||||
import android.os.Build;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import org.spongycastle.asn1.ASN1Boolean;
|
||||
import org.spongycastle.asn1.ASN1Encodable;
|
||||
import org.spongycastle.asn1.ASN1EncodableVector;
|
||||
import org.spongycastle.asn1.ASN1Enumerated;
|
||||
import org.spongycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.spongycastle.asn1.ASN1OctetString;
|
||||
import org.spongycastle.asn1.ASN1Sequence;
|
||||
import org.spongycastle.asn1.ASN1TaggedObject;
|
||||
import org.spongycastle.asn1.DEROctetString;
|
||||
import org.spongycastle.asn1.DERSequence;
|
||||
import org.spongycastle.asn1.DERTaggedObject;
|
||||
import org.spongycastle.asn1.x509.Extension;
|
||||
import org.spongycastle.cert.X509CertificateHolder;
|
||||
import org.spongycastle.cert.X509v3CertificateBuilder;
|
||||
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.spongycastle.openssl.PEMKeyPair;
|
||||
import org.spongycastle.openssl.PEMParser;
|
||||
import org.spongycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.spongycastle.operator.ContentSigner;
|
||||
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.spongycastle.util.io.pem.PemReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public final class Android {
|
||||
private static final String TAG = "chiteroman";
|
||||
private static final PEMKeyPair EC, RSA;
|
||||
private static final ASN1ObjectIdentifier OID = new ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17");
|
||||
private static final List<Certificate> EC_CERTS = new ArrayList<>();
|
||||
private static final List<Certificate> RSA_CERTS = new ArrayList<>();
|
||||
private static final Map<String, String> map = new HashMap<>();
|
||||
private static final CertificateFactory certificateFactory;
|
||||
|
||||
static {
|
||||
map.put("MANUFACTURER", "Google");
|
||||
map.put("MODEL", "Pixel");
|
||||
map.put("FINGERPRINT", "google/sailfish/sailfish:8.1.0/OPM1.171019.011/4448085:user/release-keys");
|
||||
map.put("BRAND", "google");
|
||||
map.put("PRODUCT", "sailfish");
|
||||
map.put("DEVICE", "sailfish");
|
||||
map.put("RELEASE", "8.1.0");
|
||||
map.put("ID", "OPM1.171019.011");
|
||||
map.put("INCREMENTAL", "4448085");
|
||||
map.put("SECURITY_PATCH", "2017-12-05");
|
||||
map.put("TYPE", "user");
|
||||
map.put("TAGS", "release-keys");
|
||||
try {
|
||||
certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
|
||||
EC = parseKeyPair(Keybox.EC.PRIVATE_KEY);
|
||||
EC_CERTS.add(parseCert(Keybox.EC.CERTIFICATE_1));
|
||||
EC_CERTS.add(parseCert(Keybox.EC.CERTIFICATE_2));
|
||||
|
||||
RSA = parseKeyPair(Keybox.RSA.PRIVATE_KEY);
|
||||
RSA_CERTS.add(parseCert(Keybox.RSA.CERTIFICATE_1));
|
||||
RSA_CERTS.add(parseCert(Keybox.RSA.CERTIFICATE_2));
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, t.toString());
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
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 Field getField(String fieldName) {
|
||||
Field field = null;
|
||||
try {
|
||||
field = Build.class.getDeclaredField(fieldName);
|
||||
} catch (Throwable ignored) {
|
||||
try {
|
||||
field = Build.VERSION.class.getDeclaredField(fieldName);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "Couldn't find field " + fieldName);
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
public static Certificate[] engineGetCertificateChain(Certificate[] caList) {
|
||||
if (caList == null) throw new UnsupportedOperationException();
|
||||
try {
|
||||
X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded()));
|
||||
|
||||
byte[] bytes = leaf.getExtensionValue(OID.getId());
|
||||
|
||||
if (bytes == null) return caList;
|
||||
|
||||
X509CertificateHolder holder = new X509CertificateHolder(leaf.getEncoded());
|
||||
|
||||
Extension ext = holder.getExtension(OID);
|
||||
|
||||
ASN1Sequence sequence = ASN1Sequence.getInstance(ext.getExtnValue().getOctets());
|
||||
|
||||
ASN1Encodable[] encodables = sequence.toArray();
|
||||
|
||||
ASN1Sequence teeEnforced = (ASN1Sequence) encodables[7];
|
||||
|
||||
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||
|
||||
for (ASN1Encodable asn1Encodable : teeEnforced) {
|
||||
ASN1TaggedObject taggedObject = (ASN1TaggedObject) asn1Encodable;
|
||||
if (taggedObject.getTagNo() == 704) continue;
|
||||
vector.add(taggedObject);
|
||||
}
|
||||
|
||||
LinkedList<Certificate> certificates;
|
||||
|
||||
X509v3CertificateBuilder builder;
|
||||
ContentSigner signer;
|
||||
|
||||
if (KeyProperties.KEY_ALGORITHM_EC.equals(leaf.getPublicKey().getAlgorithm())) {
|
||||
certificates = new LinkedList<>(EC_CERTS);
|
||||
builder = new X509v3CertificateBuilder(new X509CertificateHolder(EC_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), EC.getPublicKeyInfo());
|
||||
signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(EC.getPrivateKeyInfo()));
|
||||
} else {
|
||||
certificates = new LinkedList<>(RSA_CERTS);
|
||||
builder = new X509v3CertificateBuilder(new X509CertificateHolder(RSA_CERTS.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), RSA.getPublicKeyInfo());
|
||||
signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(RSA.getPrivateKeyInfo()));
|
||||
}
|
||||
|
||||
byte[] verifiedBootKey = new byte[32];
|
||||
byte[] verifiedBootHash = new byte[32];
|
||||
|
||||
ThreadLocalRandom.current().nextBytes(verifiedBootKey);
|
||||
ThreadLocalRandom.current().nextBytes(verifiedBootHash);
|
||||
|
||||
ASN1Encodable[] rootOfTrustEnc = {new DEROctetString(verifiedBootKey), ASN1Boolean.TRUE, new ASN1Enumerated(0), new DEROctetString(verifiedBootHash)};
|
||||
|
||||
ASN1Sequence rootOfTrustSeq = new DERSequence(rootOfTrustEnc);
|
||||
|
||||
ASN1TaggedObject rootOfTrustTagObj = new DERTaggedObject(704, rootOfTrustSeq);
|
||||
|
||||
vector.add(rootOfTrustTagObj);
|
||||
|
||||
ASN1Sequence hackEnforced = new DERSequence(vector);
|
||||
|
||||
encodables[7] = hackEnforced;
|
||||
|
||||
ASN1Sequence hackedSeq = new DERSequence(encodables);
|
||||
|
||||
ASN1OctetString hackedSeqOctets = new DEROctetString(hackedSeq);
|
||||
|
||||
Extension hackedExt = new Extension(OID, false, hackedSeqOctets);
|
||||
|
||||
builder.addExtension(hackedExt);
|
||||
|
||||
for (ASN1ObjectIdentifier extensionOID : holder.getExtensions().getExtensionOIDs()) {
|
||||
if (OID.getId().equals(extensionOID.getId())) continue;
|
||||
builder.addExtension(holder.getExtension(extensionOID));
|
||||
}
|
||||
|
||||
certificates.addFirst(new JcaX509CertificateConverter().getCertificate(builder.build(signer)));
|
||||
|
||||
return certificates.toArray(new Certificate[0]);
|
||||
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, t.toString());
|
||||
}
|
||||
return caList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package io.github.a13e300.tricky_store.fwpatch;
|
||||
|
||||
public final class Keybox {
|
||||
public static final class EC {
|
||||
public static final String PRIVATE_KEY = """
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
|
||||
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
|
||||
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
|
||||
-----END EC PRIVATE KEY-----
|
||||
""";
|
||||
public static final String CERTIFICATE_1 = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
|
||||
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
|
||||
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
|
||||
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
|
||||
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
|
||||
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
|
||||
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
|
||||
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
|
||||
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
|
||||
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
|
||||
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
|
||||
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
|
||||
Xvsiu+f+uXc/WT/7
|
||||
-----END CERTIFICATE-----
|
||||
""";
|
||||
public static final String CERTIFICATE_2 = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
|
||||
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
|
||||
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
|
||||
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
|
||||
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
|
||||
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
||||
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
|
||||
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
|
||||
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
|
||||
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
|
||||
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
|
||||
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
|
||||
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
|
||||
-----END CERTIFICATE-----
|
||||
""";
|
||||
}
|
||||
|
||||
public static final class RSA {
|
||||
public static final String PRIVATE_KEY = """
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
|
||||
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
|
||||
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
|
||||
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
|
||||
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
|
||||
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
|
||||
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
|
||||
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
|
||||
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
|
||||
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
|
||||
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
|
||||
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
|
||||
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
|
||||
-----END RSA PRIVATE KEY-----
|
||||
""";
|
||||
public static final String CERTIFICATE_1 = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
|
||||
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
||||
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
|
||||
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
|
||||
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
|
||||
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
|
||||
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
|
||||
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
|
||||
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
|
||||
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
|
||||
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
|
||||
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
|
||||
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
|
||||
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
|
||||
-----END CERTIFICATE-----
|
||||
""";
|
||||
public static final String CERTIFICATE_2 = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
|
||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
|
||||
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
|
||||
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
|
||||
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
|
||||
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
|
||||
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
|
||||
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
|
||||
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
|
||||
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
|
||||
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
|
||||
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
|
||||
Lvtc9mL1J1IXvmM=
|
||||
-----END CERTIFICATE-----
|
||||
""";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.github.a13e300.tricky_store.fwpatch;
|
||||
|
||||
import android.system.keystore2.KeyEntryResponse;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class Utils {
|
||||
private final static String TAG = "Utils";
|
||||
static X509Certificate toCertificate(byte[] bytes) {
|
||||
try {
|
||||
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) certFactory.generateCertificate(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(TAG, "Couldn't parse certificate in keystore", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Collection<X509Certificate> toCertificates(byte[] bytes) {
|
||||
try {
|
||||
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
return (Collection<X509Certificate>) certFactory.generateCertificates(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(TAG, "Couldn't parse certificates in keystore", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static Certificate[] getCertificateChain(KeyEntryResponse response) {
|
||||
if (response == null || response.metadata.certificate == null) return null;
|
||||
var leaf = toCertificate(response.metadata.certificate);
|
||||
Certificate[] chain;
|
||||
if (response.metadata.certificateChain != null) {
|
||||
var certs = toCertificates(response.metadata.certificateChain);
|
||||
chain = new Certificate[certs.size() + 1];
|
||||
final Iterator<X509Certificate> it = certs.iterator();
|
||||
int i = 1;
|
||||
while (it.hasNext()) {
|
||||
chain[i++] = it.next();
|
||||
}
|
||||
} else {
|
||||
chain = new Certificate[1];
|
||||
}
|
||||
chain[0] = leaf;
|
||||
return chain;
|
||||
}
|
||||
|
||||
public static void putCertificateChain(KeyEntryResponse response, Certificate[] chain) throws Throwable {
|
||||
if (chain == null || chain.length == 0) return;
|
||||
response.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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user