From 236384b398c64a7b623ab781f65fc752e64087ce Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Thu, 11 Jul 2024 15:38:27 +0800 Subject: [PATCH] support configuration --- .../github/a13e300/tricky_store/Logger.java | 19 ++++ .../io/github/a13e300/tricky_store/Main.kt | 56 ++++++++- .../a13e300/tricky_store/fwpatch/Android.java | 84 ++++++++------ .../a13e300/tricky_store/fwpatch/Keybox.java | 107 ------------------ .../tricky_store/fwpatch/XMLParser.java | 96 ++++++++++++++++ 5 files changed, 220 insertions(+), 142 deletions(-) create mode 100644 service/src/main/java/io/github/a13e300/tricky_store/Logger.java delete mode 100644 service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java create mode 100644 service/src/main/java/io/github/a13e300/tricky_store/fwpatch/XMLParser.java diff --git a/service/src/main/java/io/github/a13e300/tricky_store/Logger.java b/service/src/main/java/io/github/a13e300/tricky_store/Logger.java new file mode 100644 index 0000000..bebd1b3 --- /dev/null +++ b/service/src/main/java/io/github/a13e300/tricky_store/Logger.java @@ -0,0 +1,19 @@ +package io.github.a13e300.tricky_store; + +import android.util.Log; + +public class Logger { + private static final String TAG = "TrickyStore"; + public static void d(String msg) { + Log.d(TAG, msg); + } + + public static void e(String msg) { + Log.e(TAG, msg); + } + + public static void e(String msg, Throwable t) { + Log.e(TAG, msg, t); + } + +} diff --git a/service/src/main/java/io/github/a13e300/tricky_store/Main.kt b/service/src/main/java/io/github/a13e300/tricky_store/Main.kt index c65fe5c..c610843 100644 --- a/service/src/main/java/io/github/a13e300/tricky_store/Main.kt +++ b/service/src/main/java/io/github/a13e300/tricky_store/Main.kt @@ -3,7 +3,9 @@ package io.github.a13e300.tricky_store import android.annotation.SuppressLint import android.content.pm.IPackageManager import android.os.Binder +import android.os.FileObserver import android.os.IBinder +import android.os.Looper import android.os.Parcel import android.os.ServiceManager import android.system.keystore2.IKeystoreService @@ -11,6 +13,7 @@ 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 java.io.File import kotlin.system.exitProcess open class BinderInterceptor : Binder() { @@ -113,7 +116,8 @@ fun registerBinderInterceptor(backdoor: IBinder, target: IBinder, interceptor: B backdoor.transact(1, data, reply, 0) } -val targetPackages = arrayOf("com.google.android.gms", "icu.nullptr.nativetest", "io.github.vvb2060.mahoshojo", "io.github.vvb2060.keyattestation") +val targetPackages = mutableSetOf() +val DEFAULT_TARGET_PACKAGES = listOf("com.google.android.gms", "icu.nullptr.nativetest", "io.github.vvb2060.mahoshojo", "io.github.vvb2060.keyattestation") const val TAG = "TrickyStore" @@ -138,6 +142,40 @@ fun getPm(): IPackageManager? { return iPm } +fun updateTargetPackages(f: File) = runCatching { + targetPackages.clear() + f.readLines().mapNotNullTo(targetPackages) { + if (it.isNotBlank() && !it.startsWith("#")) it else null + } + Logger.d("update target packages: $targetPackages") +}.onFailure { + Logger.e("failed to update target files", it) +} + +fun updateKeyBox(f: File) = runCatching { + Android.readFromXml(f.readText()) +}.onFailure { + Logger.e("failed to update keybox", it) +} + +const val CONFIG_PATH = "/data/adb/tricky_store" +const val TARGET_FILE = "target.txt" +const val KEYBOX_FILE = "keybox.xml" + +class ConfigObserver : FileObserver(CONFIG_PATH, CLOSE_WRITE) { + val root = File(CONFIG_PATH) + override fun onEvent(event: Int, path: String?) { + path ?: return + if (event == CLOSE_WRITE) { + val f = File(root, path) + when (f.name) { + TARGET_FILE -> updateTargetPackages(f) + KEYBOX_FILE -> updateKeyBox(f) + } + } + } +} + @SuppressLint("BlockedPrivateApi") fun tryRunKeystoreInterceptor(): Boolean { val b = ServiceManager.getService("android.system.keystore2.IKeystoreService/default") ?: return false @@ -195,6 +233,22 @@ fun tryRunKeystoreInterceptor(): Boolean { return Skip } } + val path = File(CONFIG_PATH) + path.mkdirs() + val target = File(path, TARGET_FILE) + if (!target.exists()) { + target.createNewFile() + target.writeText(DEFAULT_TARGET_PACKAGES.joinToString("\n")) + } + updateTargetPackages(target) + val keybox = File(path, KEYBOX_FILE) + if (!keybox.exists()) { + Logger.e("keybox file not found, please put it to $keybox !") + } else { + updateKeyBox(keybox) + } + val monitor = ConfigObserver() + monitor.startWatching() registerBinderInterceptor(bd, b, interceptor) while (true) { Thread.sleep(1000000) diff --git a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java index 40c24ca..388ddce 100644 --- a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java +++ b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Android.java @@ -37,46 +37,65 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; +import io.github.a13e300.tricky_store.Logger; + 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 EC_CERTS = new ArrayList<>(); - private static final List RSA_CERTS = new ArrayList<>(); - private static final Map map = new HashMap<>(); + + record KeyBox(PEMKeyPair privateKey, List certificates) {} + private static final Map keyboxes = 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); } } + public static void readFromXml(String data) { + keyboxes.clear(); + XMLParser xmlParser = new XMLParser(data); + + try { + int numberOfKeyboxes = Integer.parseInt(Objects.requireNonNull(xmlParser.obtainPath( + "AndroidAttestation.NumberOfKeyboxes").get("text"))); + for (int i = 0; i < numberOfKeyboxes; i++) { + String keyboxAlgorithm = xmlParser.obtainPath( + "AndroidAttestation.Keybox.Key[" + i + "]").get("algorithm"); + String privateKey = xmlParser.obtainPath( + "AndroidAttestation.Keybox.Key[" + i + "].PrivateKey").get("text"); + int numberOfCertificates = Integer.parseInt(Objects.requireNonNull(xmlParser.obtainPath( + "AndroidAttestation.Keybox.Key[" + i + "].CertificateChain.NumberOfCertificates").get("text"))); + + LinkedList certificateChain = new LinkedList<>(); + + for (int j = 0; j < numberOfCertificates; j++) { + Map certData= xmlParser.obtainPath( + "AndroidAttestation.Keybox.Key[" + i + "].CertificateChain.Certificate[" + j + "]"); + certificateChain.add(parseCert(certData.get("text"))); + } + String algo; + if (keyboxAlgorithm.toLowerCase().equals("ecdsa")) { + algo = KeyProperties.KEY_ALGORITHM_EC; + } else { + algo = KeyProperties.KEY_ALGORITHM_RSA; + } + keyboxes.put(algo, new KeyBox(parseKeyPair(privateKey), certificateChain)); + } + Logger.d("update " + numberOfKeyboxes + " keyboxes"); + } catch (Throwable t) { + Logger.e("Error loading xml file: " + t); + } + } + private static PEMKeyPair parseKeyPair(String key) throws Throwable { try (PEMParser parser = new PEMParser(new StringReader(key))) { return (PEMKeyPair) parser.readObject(); @@ -104,7 +123,7 @@ public final class Android { } public static Certificate[] engineGetCertificateChain(Certificate[] caList) { - if (caList == null) throw new UnsupportedOperationException(); + if (caList == null) throw new UnsupportedOperationException("caList is null!"); try { X509Certificate leaf = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(caList[0].getEncoded())); @@ -135,15 +154,12 @@ public final class Android { 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())); - } + var k = keyboxes.get(leaf.getPublicKey().getAlgorithm()); + if (k == null) throw new UnsupportedOperationException("unsupported algorithm " + leaf.getPublicKey().getAlgorithm()); + certificates = new LinkedList<>(k.certificates); + builder = new X509v3CertificateBuilder(new X509CertificateHolder(certificates.get(0).getEncoded()).getSubject(), holder.getSerialNumber(), holder.getNotBefore(), holder.getNotAfter(), holder.getSubject(), k.privateKey.getPublicKeyInfo()); + signer = new JcaContentSignerBuilder(leaf.getSigAlgName()).build(new JcaPEMKeyConverter().getPrivateKey(k.privateKey.getPrivateKeyInfo())); + byte[] verifiedBootKey = new byte[32]; byte[] verifiedBootHash = new byte[32]; diff --git a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java deleted file mode 100644 index 43751df..0000000 --- a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/Keybox.java +++ /dev/null @@ -1,107 +0,0 @@ -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----- - """; - } -} diff --git a/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/XMLParser.java b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/XMLParser.java new file mode 100644 index 0000000..4c16ee0 --- /dev/null +++ b/service/src/main/java/io/github/a13e300/tricky_store/fwpatch/XMLParser.java @@ -0,0 +1,96 @@ +package io.github.a13e300.tricky_store.fwpatch; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; + +public class XMLParser { + + private final String xml; + + public XMLParser(String xml) { + this.xml = xml; + } + + public Map obtainPath(String path) throws Exception { + XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance(); + XmlPullParser parser = xmlFactoryObject.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(new StringReader(xml)); + + String[] tags = path.split("\\."); + + return readData(parser, tags, 0, new HashMap<>()); + } + + private Map readData(XmlPullParser parser, String[] tags, int index, + Map tagCounts) throws IOException, XmlPullParserException { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if (name.equals(tags[index].split("\\[")[0])) { + + String[] tagParts = tags[index].split("\\["); + if (tagParts.length > 1) { + if (tagCounts.getOrDefault(name, 0) < Integer.parseInt(tagParts[1].replace("]", ""))) { + tagCounts.put(name, tagCounts.getOrDefault(name, 0) + 1); + return readData(parser, tags, index, tagCounts); + } else { + if (index == tags.length - 1) { + return readAttributes(parser); + } else { + return readData(parser, tags, index + 1, tagCounts); + } + } + } else { + if (index == tags.length - 1) { + return readAttributes(parser); + } else { + return readData(parser, tags, index + 1, tagCounts); + } + } + } else { + skip(parser); + } + } + + throw new XmlPullParserException("Path not found"); + } + + private Map readAttributes(XmlPullParser parser) throws IOException, XmlPullParserException { + Map attributes = new HashMap<>(); + for (int i = 0; i < parser.getAttributeCount(); i++) { + attributes.put(parser.getAttributeName(i), parser.getAttributeValue(i)); + } + if (parser.next() == XmlPullParser.TEXT) { + attributes.put("text", parser.getText()); + } + return attributes; + } + + private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } +} \ No newline at end of file