diff --git a/.clang-format b/.clang-format
index ab38316..b643df2 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,17 +1,17 @@
-BasedOnStyle: LLVM
-
-Language: Cpp
-Standard: c++20
-
-ColumnLimit: 135
-
-AlignEscapedNewlines: Left
-AllowShortFunctionsOnASingleLine: Empty
-AllowShortLambdasOnASingleLine: Empty
-AlwaysBreakTemplateDeclarations: true
-IndentPPDirectives: AfterHash
-
-AccessModifierOffset: -4
-IndentWidth: 4
-UseTab: Never
-
+BasedOnStyle: LLVM
+
+Language: Cpp
+Standard: c++20
+
+ColumnLimit: 135
+
+AlignEscapedNewlines: Left
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortLambdasOnASingleLine: Empty
+AlwaysBreakTemplateDeclarations: true
+IndentPPDirectives: AfterHash
+
+AccessModifierOffset: -4
+IndentWidth: 4
+UseTab: Never
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b1b618c..7c6f541 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt
index f23d04c..a6f0e6f 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt
@@ -1,188 +1,188 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS
-
-import android.content.pm.IPackageManager
-import android.content.pm.PackageManager
-import android.os.Build
-import android.os.ServiceManager
-import android.os.SystemProperties
-import io.github.beakthoven.TrickyStoreOSS.core.config.Config
-import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import org.bouncycastle.asn1.ASN1Integer
-import org.bouncycastle.asn1.DEROctetString
-import org.bouncycastle.asn1.DERSequence
-import java.security.MessageDigest
-import java.util.concurrent.ThreadLocalRandom
-
-fun getTransactCode(clazz: Class<*>, method: String): Int =
- clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
- .getInt(null)
-
-val bootHash: ByteArray by lazy {
- getBootHashFromProp() ?: randomBytes()
-}
-
-val bootKey: ByteArray by lazy {
- randomBytes()
-}
-
-@OptIn(ExperimentalStdlibApi::class)
-private fun getBootHashFromProp(): ByteArray? {
- val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
- return if (digest.length == 64) digest.hexToByteArray() else null
-}
-
-private fun randomBytes(): ByteArray = ByteArray(32).also {
- ThreadLocalRandom.current().nextBytes(it)
-}
-
-val patchLevel: Int
- get() = getCustomPatchLevel("system", false)
- ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
-
-val patchLevelLong: Int
- get() = getCustomPatchLevel("system", true)
- ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
-
-val vendorPatchLevel: Int
- get() = getCustomPatchLevel("vendor", false)
- ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
-
-val vendorPatchLevelLong: Int
- get() = getCustomPatchLevel("vendor", true)
- ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
-
-val bootPatchLevel: Int
- get() = getCustomPatchLevel("boot", false)
- ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
-
-val bootPatchLevelLong: Int
- get() = getCustomPatchLevel("boot", true)
- ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
-
-private val customPatchLevel: CustomPatchLevel?
- get() = Config._customPatchLevel
-
-private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
- val config = customPatchLevel ?: return null
- val value = when (component) {
- "system" -> config.system ?: config.all
- "vendor" -> config.vendor ?: config.all
- "boot" -> config.boot ?: config.all
- else -> config.all
- } ?: return null
-
- when {
- value.equals("no", ignoreCase = true) -> return null
- value.equals("prop", ignoreCase = true) -> return null
- }
-
- return parsePatchLevelValue(value, component, isLong)
-}
-
-private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
- val normalized = value.replace("-", "")
-
- return try {
- when (normalized.length) {
- 8 -> {
- val year = normalized.substring(0, 4).toInt()
- val month = normalized.substring(4, 6).toInt()
- val day = normalized.substring(6, 8).toInt()
- if (isLong) year * 10000 + month * 100 + day
- else year * 100 + month
- }
- 6 -> {
- val year = normalized.substring(0, 4).toInt()
- val month = normalized.substring(4, 6).toInt()
- if (isLong) year * 10000 + month * 100
- else year * 100 + month
- }
- else -> {
- Logger.e("Invalid patch level length for $component: $normalized")
- null
- }
- }
- } catch (e: NumberFormatException) {
- Logger.e("Patch level parse error for $component=$value", e)
- null
- }
-}
-
-val osVersion: Int
- get() = getOsVersion(Build.VERSION.SDK_INT)
-
-private val osVersionMap = mapOf(
- Build.VERSION_CODES.BAKLAVA to 160000,
- Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
- Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
- Build.VERSION_CODES.TIRAMISU to 130000,
- Build.VERSION_CODES.S_V2 to 120100,
- Build.VERSION_CODES.S to 120000,
- Build.VERSION_CODES.R to 110000,
- Build.VERSION_CODES.Q to 100000
-)
-
-private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000
-
-fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
- val parts = split("-")
- when {
- isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
- parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
- else -> throw IllegalArgumentException("Invalid patch level format: $this")
- }
-}.onFailure {
- Logger.e("Invalid patch level format: $this", it)
-}.getOrDefault(202404)
-
-fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getPackageInfo(name, flags, userId)
- } else {
- @Suppress("DEPRECATION")
- getPackageInfo(name, flags.toInt(), userId)
- }
-
-val apexInfos: List> by lazy {
- runCatching {
- val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
- val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
- } else {
- @Suppress("DEPRECATION")
- packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
- }
-
- packages.list
- .map { it.packageName to it.longVersionCode }
- .sortedBy { it.first }
- }.getOrElse {
- Logger.e("Failed to get APEX package information")
- emptyList()
- }
-}
-
-val moduleHash: ByteArray by lazy {
- runCatching {
- val encodables = apexInfos.flatMap { (packageName, versionCode) ->
- listOf(
- DEROctetString(packageName.toByteArray()),
- ASN1Integer(versionCode)
- )
- }
-
- val sequence = DERSequence(encodables.toTypedArray())
- MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
- }.getOrElse {
- Logger.e("Failed to compute module hash", it)
- ByteArray(32)
- }
-}
-
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS
+
+import android.content.pm.IPackageManager
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.ServiceManager
+import android.os.SystemProperties
+import io.github.beakthoven.TrickyStoreOSS.core.config.Config
+import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import org.bouncycastle.asn1.ASN1Integer
+import org.bouncycastle.asn1.DEROctetString
+import org.bouncycastle.asn1.DERSequence
+import java.security.MessageDigest
+import java.util.concurrent.ThreadLocalRandom
+
+fun getTransactCode(clazz: Class<*>, method: String): Int =
+ clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
+ .getInt(null)
+
+val bootHash: ByteArray by lazy {
+ getBootHashFromProp() ?: randomBytes()
+}
+
+val bootKey: ByteArray by lazy {
+ randomBytes()
+}
+
+@OptIn(ExperimentalStdlibApi::class)
+private fun getBootHashFromProp(): ByteArray? {
+ val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
+ return if (digest.length == 64) digest.hexToByteArray() else null
+}
+
+private fun randomBytes(): ByteArray = ByteArray(32).also {
+ ThreadLocalRandom.current().nextBytes(it)
+}
+
+val patchLevel: Int
+ get() = getCustomPatchLevel("system", false)
+ ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
+
+val patchLevelLong: Int
+ get() = getCustomPatchLevel("system", true)
+ ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
+
+val vendorPatchLevel: Int
+ get() = getCustomPatchLevel("vendor", false)
+ ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
+
+val vendorPatchLevelLong: Int
+ get() = getCustomPatchLevel("vendor", true)
+ ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
+
+val bootPatchLevel: Int
+ get() = getCustomPatchLevel("boot", false)
+ ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
+
+val bootPatchLevelLong: Int
+ get() = getCustomPatchLevel("boot", true)
+ ?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
+
+private val customPatchLevel: CustomPatchLevel?
+ get() = Config._customPatchLevel
+
+private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
+ val config = customPatchLevel ?: return null
+ val value = when (component) {
+ "system" -> config.system ?: config.all
+ "vendor" -> config.vendor ?: config.all
+ "boot" -> config.boot ?: config.all
+ else -> config.all
+ } ?: return null
+
+ when {
+ value.equals("no", ignoreCase = true) -> return null
+ value.equals("prop", ignoreCase = true) -> return null
+ }
+
+ return parsePatchLevelValue(value, component, isLong)
+}
+
+private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
+ val normalized = value.replace("-", "")
+
+ return try {
+ when (normalized.length) {
+ 8 -> {
+ val year = normalized.substring(0, 4).toInt()
+ val month = normalized.substring(4, 6).toInt()
+ val day = normalized.substring(6, 8).toInt()
+ if (isLong) year * 10000 + month * 100 + day
+ else year * 100 + month
+ }
+ 6 -> {
+ val year = normalized.substring(0, 4).toInt()
+ val month = normalized.substring(4, 6).toInt()
+ if (isLong) year * 10000 + month * 100
+ else year * 100 + month
+ }
+ else -> {
+ Logger.e("Invalid patch level length for $component: $normalized")
+ null
+ }
+ }
+ } catch (e: NumberFormatException) {
+ Logger.e("Patch level parse error for $component=$value", e)
+ null
+ }
+}
+
+val osVersion: Int
+ get() = getOsVersion(Build.VERSION.SDK_INT)
+
+private val osVersionMap = mapOf(
+ Build.VERSION_CODES.BAKLAVA to 160000,
+ Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
+ Build.VERSION_CODES.TIRAMISU to 130000,
+ Build.VERSION_CODES.S_V2 to 120100,
+ Build.VERSION_CODES.S to 120000,
+ Build.VERSION_CODES.R to 110000,
+ Build.VERSION_CODES.Q to 100000
+)
+
+private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000
+
+fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
+ val parts = split("-")
+ when {
+ isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
+ parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
+ else -> throw IllegalArgumentException("Invalid patch level format: $this")
+ }
+}.onFailure {
+ Logger.e("Invalid patch level format: $this", it)
+}.getOrDefault(202404)
+
+fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ getPackageInfo(name, flags, userId)
+ } else {
+ @Suppress("DEPRECATION")
+ getPackageInfo(name, flags.toInt(), userId)
+ }
+
+val apexInfos: List> by lazy {
+ runCatching {
+ val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
+ val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
+ } else {
+ @Suppress("DEPRECATION")
+ packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
+ }
+
+ packages.list
+ .map { it.packageName to it.longVersionCode }
+ .sortedBy { it.first }
+ }.getOrElse {
+ Logger.e("Failed to get APEX package information")
+ emptyList()
+ }
+}
+
+val moduleHash: ByteArray by lazy {
+ runCatching {
+ val encodables = apexInfos.flatMap { (packageName, versionCode) ->
+ listOf(
+ DEROctetString(packageName.toByteArray()),
+ ASN1Integer(versionCode)
+ )
+ }
+
+ val sequence = DERSequence(encodables.toTypedArray())
+ MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
+ }.getOrElse {
+ Logger.e("Failed to compute module hash", it)
+ ByteArray(32)
+ }
+}
+
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt
index 445c3e3..47e2846 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt
@@ -1,868 +1,868 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS
-
-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 io.github.beakthoven.TrickyStoreOSS.*
-import io.github.beakthoven.TrickyStoreOSS.core.config.Config
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor
-import org.bouncycastle.asn1.*
-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
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
-import org.bouncycastle.util.io.pem.PemReader
-import java.io.ByteArrayInputStream
-import java.io.StringReader
-import java.math.BigInteger
-import java.nio.charset.StandardCharsets
-import java.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.*
-import java.util.concurrent.ConcurrentHashMap
-import javax.security.auth.x500.X500Principal
-
-object CertificateHacker {
-
- private val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
-
- private val certificateFactory: CertificateFactory by lazy {
- try {
- CertificateFactory.getInstance("X.509")
- } catch (t: Throwable) {
- Logger.e("Failed to initialize certificate factory", t)
- throw RuntimeException("Cannot initialize certificate factory", t)
- }
- }
-
- data class KeyBox(
- val pemKeyPair: PEMKeyPair,
- val keyPair: KeyPair,
- val certificates: List
- )
-
- data class KeyIdentifier(
- val alias: String,
- val uid: Int
- )
-
- sealed class ParseResult {
- data class Success(val data: T) : ParseResult()
- data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
- }
-
- sealed class HackResult {
- data class Success(val data: T) : HackResult()
- data class Error(val message: String, val cause: Throwable? = null) : HackResult()
- }
-
- data class KeyGenParameters(
- var keySize: Int = 0,
- var algorithm: Int = 0,
- var certificateSerial: BigInteger? = null,
- var certificateNotBefore: Date? = null,
- var certificateNotAfter: Date? = null,
- var certificateSubject: X500Name? = null,
- var rsaPublicExponent: BigInteger? = null,
- var ecCurve: Int = 0,
- var ecCurveName: String? = null,
- var purpose: MutableList = mutableListOf(),
- var digest: MutableList = mutableListOf(),
- var attestationChallenge: ByteArray? = null,
- var brand: ByteArray? = null,
- var device: ByteArray? = null,
- var product: ByteArray? = null,
- var manufacturer: ByteArray? = null,
- var model: ByteArray? = null,
- var imei1: ByteArray? = null,
- var imei2: ByteArray? = null,
- var meid: ByteArray? = null,
- var serialno: ByteArray? = null
- ) {
-
- constructor(params: Array) : this() {
- parseKeyParameters(params)
- }
-
- private fun parseKeyParameters(params: Array) {
- params.forEach { param ->
- Logger.d("Processing key parameter: ${param.tag}")
- val value = param.value
-
- when (param.tag) {
- Tag.KEY_SIZE -> keySize = value.integer
- Tag.ALGORITHM -> algorithm = value.algorithm
- Tag.CERTIFICATE_SERIAL -> certificateSerial = BigInteger(value.blob)
- Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = Date(value.dateTime)
- Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = Date(value.dateTime)
- Tag.CERTIFICATE_SUBJECT -> certificateSubject = X500Name(X500Principal(value.blob).name)
- Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
- Tag.EC_CURVE -> {
- ecCurve = value.ecCurve
- ecCurveName = getEcCurveName(ecCurve)
- }
- Tag.PURPOSE -> purpose.add(value.keyPurpose)
- Tag.DIGEST -> digest.add(value.digest)
- Tag.ATTESTATION_CHALLENGE -> attestationChallenge = value.blob
- Tag.ATTESTATION_ID_BRAND -> brand = value.blob
- Tag.ATTESTATION_ID_DEVICE -> device = value.blob
- Tag.ATTESTATION_ID_PRODUCT -> product = value.blob
- Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = value.blob
- Tag.ATTESTATION_ID_MODEL -> model = value.blob
- Tag.ATTESTATION_ID_IMEI -> imei1 = value.blob
- Tag.ATTESTATION_ID_SECOND_IMEI -> imei2 = value.blob
- Tag.ATTESTATION_ID_MEID -> meid = value.blob
- }
- }
- }
-
- fun setEcCurveName(curveSize: Int) {
- ecCurveName = when (curveSize) {
- 224 -> "secp224r1"
- 256 -> "secp256r1"
- 384 -> "secp384r1"
- 521 -> "secp521r1"
- else -> "secp256r1"
- }
- }
-
- companion object {
- private fun getEcCurveName(curve: Int): String = when (curve) {
- EcCurve.CURVE_25519 -> "CURVE_25519"
- EcCurve.P_224 -> "secp224r1"
- EcCurve.P_256 -> "secp256r1"
- EcCurve.P_384 -> "secp384r1"
- EcCurve.P_521 -> "secp521r1"
- else -> throw IllegalArgumentException("Unknown EC curve: $curve")
- }
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
-
- other as KeyGenParameters
-
- return keySize == other.keySize &&
- algorithm == other.algorithm &&
- certificateSerial == other.certificateSerial &&
- certificateNotBefore == other.certificateNotBefore &&
- certificateNotAfter == other.certificateNotAfter &&
- certificateSubject == other.certificateSubject &&
- rsaPublicExponent == other.rsaPublicExponent &&
- ecCurve == other.ecCurve &&
- ecCurveName == other.ecCurveName &&
- purpose == other.purpose &&
- digest == other.digest &&
- attestationChallenge.contentEquals(other.attestationChallenge) &&
- brand.contentEquals(other.brand) &&
- device.contentEquals(other.device) &&
- product.contentEquals(other.product) &&
- manufacturer.contentEquals(other.manufacturer) &&
- model.contentEquals(other.model) &&
- imei1.contentEquals(other.imei1) &&
- imei2.contentEquals(other.imei2) &&
- meid.contentEquals(other.meid) &&
- serialno.contentEquals(other.serialno)
- }
-
- override fun hashCode(): Int {
- var result = keySize
- result = 31 * result + algorithm
- result = 31 * result + (certificateSerial?.hashCode() ?: 0)
- result = 31 * result + (certificateNotBefore?.hashCode() ?: 0)
- result = 31 * result + (certificateNotAfter?.hashCode() ?: 0)
- result = 31 * result + (certificateSubject?.hashCode() ?: 0)
- result = 31 * result + (rsaPublicExponent?.hashCode() ?: 0)
- result = 31 * result + ecCurve
- result = 31 * result + (ecCurveName?.hashCode() ?: 0)
- result = 31 * result + purpose.hashCode()
- result = 31 * result + digest.hashCode()
- result = 31 * result + (attestationChallenge?.contentHashCode() ?: 0)
- result = 31 * result + (brand?.contentHashCode() ?: 0)
- result = 31 * result + (device?.contentHashCode() ?: 0)
- result = 31 * result + (product?.contentHashCode() ?: 0)
- result = 31 * result + (manufacturer?.contentHashCode() ?: 0)
- result = 31 * result + (model?.contentHashCode() ?: 0)
- result = 31 * result + (imei1?.contentHashCode() ?: 0)
- result = 31 * result + (imei2?.contentHashCode() ?: 0)
- result = 31 * result + (meid?.contentHashCode() ?: 0)
- result = 31 * result + (serialno?.contentHashCode() ?: 0)
- return result
- }
- }
-
- private val keyboxes = ConcurrentHashMap()
- private val leafAlgorithm = ConcurrentHashMap()
-
- private const val ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0
- private const val ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1
- private const val ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0
- private const val ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1
-
- fun canHack(): Boolean = keyboxes.isNotEmpty()
-
- private data class Digest(val digest: ByteArray) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
- other as Digest
- return digest.contentEquals(other.digest)
- }
-
- override fun hashCode(): Int = digest.contentHashCode()
- }
-
- private fun parseKeyPair(keyContent: String): ParseResult {
- return try {
- PEMParser(StringReader(keyContent.trimLine())).use { parser ->
- val pemObject = parser.readObject()
- if (pemObject is PEMKeyPair) {
- ParseResult.Success(pemObject)
- } else {
- ParseResult.Error("Invalid PEM key pair format")
- }
- }
- } catch (t: Throwable) {
- ParseResult.Error("Failed to parse PEM key pair", t)
- }
- }
-
- private fun parseCertificate(certContent: String): ParseResult {
- return try {
- PemReader(StringReader(certContent.trimLine())).use { reader ->
- val pemObject = reader.readPemObject()
- val certificate = certificateFactory.generateCertificate(
- ByteArrayInputStream(pemObject.content)
- )
- ParseResult.Success(certificate)
- }
- } catch (t: Throwable) {
- ParseResult.Error("Failed to parse certificate", t)
- }
- }
-
- @Throws(CertificateParsingException::class)
- private fun getByteArrayFromAsn1(asn1Encodable: ASN1Encodable): ByteArray {
- return when (asn1Encodable) {
- is DEROctetString -> asn1Encodable.octets
- else -> throw CertificateParsingException("Expected DEROctetString, got ${asn1Encodable::class.simpleName}")
- }
- }
-
- fun readFromXml(xmlData: String?) {
- keyboxes.clear()
- leafAlgorithm.clear()
-
- if (xmlData == null) {
- Logger.i("Clearing all keyboxes")
- return
- }
-
- try {
- val xmlParser = XmlParser(xmlData)
-
- val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
- val numberOfKeyboxes = when (numberOfKeyboxesResult) {
- is XmlParser.ParseResult.Success -> numberOfKeyboxesResult.attributes["text"]?.toIntOrNull()
- ?: throw IllegalArgumentException("Invalid number of keyboxes")
- is XmlParser.ParseResult.Error -> throw Exception(numberOfKeyboxesResult.message, numberOfKeyboxesResult.cause)
- }
-
- repeat(numberOfKeyboxes) { i ->
- processKeybox(xmlParser, i)
- }
-
- Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
- } catch (t: Throwable) {
- Logger.e("Error loading XML file (keyboxes cleared)", t)
- }
- }
-
- private fun processKeybox(xmlParser: XmlParser, index: Int) {
- try {
- val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
- val keyboxAlgorithm = when (algorithmResult) {
- is XmlParser.ParseResult.Success -> algorithmResult.attributes["algorithm"]
- ?: throw IllegalArgumentException("Missing algorithm attribute")
- is XmlParser.ParseResult.Error -> throw Exception(algorithmResult.message, algorithmResult.cause)
- }
-
- val privateKeyResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
- val privateKeyContent = when (privateKeyResult) {
- is XmlParser.ParseResult.Success -> privateKeyResult.attributes["text"]
- ?: throw IllegalArgumentException("Missing private key text")
- is XmlParser.ParseResult.Error -> throw Exception(privateKeyResult.message, privateKeyResult.cause)
- }
-
- val numberOfCertificatesResult = xmlParser.obtainPath(
- "AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
- )
- val numberOfCertificates = when (numberOfCertificatesResult) {
- is XmlParser.ParseResult.Success -> numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
- ?: throw IllegalArgumentException("Invalid number of certificates")
- is XmlParser.ParseResult.Error -> throw Exception(numberOfCertificatesResult.message, numberOfCertificatesResult.cause)
- }
-
- val certificateChain = mutableListOf()
- repeat(numberOfCertificates) { j ->
- val certResult = xmlParser.obtainPath(
- "AndroidAttestation.Keybox.Key[$index].CertificateChain.Certificate[$j]"
- )
- val certContent = when (certResult) {
- is XmlParser.ParseResult.Success -> certResult.attributes["text"]
- ?: throw IllegalArgumentException("Missing certificate text")
- is XmlParser.ParseResult.Error -> throw Exception(certResult.message, certResult.cause)
- }
-
- when (val certParseResult = parseCertificate(certContent)) {
- is ParseResult.Success -> certificateChain.add(certParseResult.data)
- is ParseResult.Error -> throw Exception(certParseResult.message, certParseResult.cause)
- }
- }
-
- val pemKeyPair = when (val keyParseResult = parseKeyPair(privateKeyContent)) {
- is ParseResult.Success -> keyParseResult.data
- is ParseResult.Error -> throw Exception(keyParseResult.message, keyParseResult.cause)
- }
-
- val keyPair = JcaPEMKeyConverter().getKeyPair(pemKeyPair)
-
- val algorithmName = when (keyboxAlgorithm.lowercase()) {
- "ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
- "rsa" -> KeyProperties.KEY_ALGORITHM_RSA
- else -> keyboxAlgorithm
- }
-
- keyboxes[algorithmName] = KeyBox(pemKeyPair, keyPair, certificateChain)
-
- } catch (t: Throwable) {
- Logger.e("Error processing keybox $index", t)
- throw t
- }
- }
-
- fun hackCertificateChain(certificateChain: Array?): Array {
- if (certificateChain == null) {
- throw UnsupportedOperationException("Certificate chain is null!")
- }
-
- return try {
- val leaf = certificateFactory.generateCertificate(
- ByteArrayInputStream(certificateChain[0].encoded)
- ) as X509Certificate
-
- val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
- ?: return certificateChain // No attestation extension, return original
-
- hackCertificateWithAttestation(leaf, certificateChain)
- } catch (t: Throwable) {
- Logger.e("Failed to hack certificate chain", t)
- certificateChain
- }
- }
-
- fun hackCertificateChainCA(caList: ByteArray?, alias: String, uid: Int): ByteArray {
- if (caList == null) {
- throw UnsupportedOperationException("CA list is null!")
- }
-
- return try {
- val key = KeyIdentifier(alias, uid)
- val algorithm = leafAlgorithm.remove(key)
- ?: throw UnsupportedOperationException("No algorithm found for key $key")
-
- val keybox = keyboxes[algorithm]
- ?: throw UnsupportedOperationException("Unsupported algorithm: $algorithm")
-
- CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
- } catch (t: Throwable) {
- Logger.e("Failed to hack CA certificate chain", t)
- caList
- }
- }
-
- fun hackCertificateChainUSR(certificate: ByteArray?, alias: String, uid: Int): ByteArray {
- if (certificate == null) {
- throw UnsupportedOperationException("Leaf certificate is null!")
- }
-
- return try {
- val leaf = certificateFactory.generateCertificate(
- ByteArrayInputStream(certificate)
- ) as X509Certificate
-
- val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
- ?: return certificate // No attestation extension, return original
-
- val keyIdentifier = KeyIdentifier(alias, uid)
- leafAlgorithm[keyIdentifier] = leaf.publicKey.algorithm
-
- hackSingleCertificate(leaf)?.encoded ?: certificate
- } catch (t: Throwable) {
- Logger.e("Failed to hack user certificate", t)
- certificate
- }
- }
-
- fun generateKeyPair(params: KeyGenParameters): KeyPair? {
- return try {
- when (params.algorithm) {
- Algorithm.EC -> {
- Logger.d("Generating EC keypair of size ${params.keySize}")
- buildECKeyPair(params)
- }
- Algorithm.RSA -> {
- Logger.d("Generating RSA keypair of size ${params.keySize}")
- buildRSAKeyPair(params)
- }
- else -> {
- Logger.e("Unsupported algorithm: ${params.algorithm}")
- null
- }
- }
- } catch (t: Throwable) {
- Logger.e("Failed to generate key pair", t)
- null
- }
- }
-
- fun generateChain(uid: Int, params: KeyGenParameters, keyPair: KeyPair): List? {
- return try {
- val keybox = getKeyboxForAlgorithm(params.algorithm)
- ?: return null
-
- val issuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
- val leaf = buildCertificate(keyPair, keybox, params, issuer, uid)
-
- val chain = mutableListOf().apply {
- add(leaf)
- addAll(keybox.certificates)
- }
-
- CertificateUtils.run { chain.toByteArrayList() }
- } catch (t: Throwable) {
- Logger.e("Failed to generate certificate chain", t)
- null
- }
- }
-
- fun generateKeyPair(
- uid: Int,
- descriptor: KeyDescriptor,
- attestKeyDescriptor: KeyDescriptor?,
- params: KeyGenParameters
- ): Pair>? {
- Logger.i("Requested KeyPair with alias: ${descriptor.alias}")
-
- val isAttestPurpose = attestKeyDescriptor != null
- if (isAttestPurpose) {
- Logger.i("Requested KeyPair with attestKey: ${attestKeyDescriptor?.alias}")
- }
-
- return try {
- val keyPair = generateKeyPair(params) ?: return null
- val keybox = getKeyboxForAlgorithm(params.algorithm) ?: return null
-
- val (rootKeyPair, issuer) = if (isAttestPurpose) {
- val attestInfo = getAttestationKeyInfo(uid, attestKeyDescriptor!!)
- if (attestInfo != null) {
- attestInfo.first to attestInfo.second
- } else {
- keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
- }
- } else {
- keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
- }
-
- val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, rootKeyPair)
- val chain = if (isAttestPurpose) mutableListOf() else mutableListOf().apply { addAll(keybox.certificates) }
- chain.add(0, leaf)
-
- Logger.d("Successfully generated certificate for alias: ${descriptor.alias}")
- Pair(keyPair, chain)
- } catch (t: Throwable) {
- Logger.e("Failed to generate key pair with certificates", t)
- null
- }
- }
-
- private fun buildECKeyPair(params: KeyGenParameters): KeyPair {
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
- Security.addProvider(BouncyCastleProvider())
-
- val spec = ECGenParameterSpec(params.ecCurveName)
- val keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME)
- keyPairGenerator.initialize(spec)
- return keyPairGenerator.generateKeyPair()
- }
-
- private fun buildRSAKeyPair(params: KeyGenParameters): KeyPair {
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
- Security.addProvider(BouncyCastleProvider())
-
- val spec = RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
- val keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
- keyPairGenerator.initialize(spec)
- return keyPairGenerator.generateKeyPair()
- }
-
- private fun getKeyboxForAlgorithm(algorithm: Int): KeyBox? {
- val algorithmName = when (algorithm) {
- Algorithm.EC -> KeyProperties.KEY_ALGORITHM_EC
- Algorithm.RSA -> KeyProperties.KEY_ALGORITHM_RSA
- else -> {
- Logger.e("Unsupported algorithm: $algorithm")
- return null
- }
- }
- return keyboxes[algorithmName]
- }
-
- private fun getAttestationKeyInfo(uid: Int, attestKeyDescriptor: KeyDescriptor): Pair? {
- Logger.d("Looking for attestation key: uid=$uid alias=${attestKeyDescriptor.alias}")
-
- val keyInfo = SecurityLevelInterceptor.getKeyPairs(uid, attestKeyDescriptor.alias)
- return if (keyInfo != null) {
- val issuer = X509CertificateHolder(keyInfo.second[0].encoded).subject
- Pair(keyInfo.first, issuer)
- } else {
- Logger.e("Attestation key info not found, falling back to default keybox")
- null
- }
- }
-
- private fun hackCertificateWithAttestation(leaf: X509Certificate, originalChain: Array): Array {
- val leafHolder = X509CertificateHolder(leaf.encoded)
- val extension = leafHolder.getExtension(ATTESTATION_OID)
- val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
- val encodables = sequence.toArray()
- val teeEnforced = encodables[7] as ASN1Sequence
-
- val vector = ASN1EncodableVector()
- var rootOfTrust: ASN1Encodable? = null
-
- teeEnforced.forEach { element ->
- val taggedObject = element as ASN1TaggedObject
- if (taggedObject.tagNo == 704) {
- rootOfTrust = taggedObject.baseObject.toASN1Primitive()
- } else {
- vector.add(taggedObject)
- }
- }
-
- val keybox = keyboxes[leaf.publicKey.algorithm]
- ?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
-
- val certificates = LinkedList(keybox.certificates)
- val builder = X509v3CertificateBuilder(
- X509CertificateHolder(certificates[0].encoded).subject,
- leafHolder.serialNumber,
- leafHolder.notBefore,
- leafHolder.notAfter,
- leafHolder.subject,
- leafHolder.subjectPublicKeyInfo
- )
-
- val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
-
- val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
- builder.addExtension(hackedExtension)
-
- leafHolder.extensions.extensionOIDs.forEach { oid ->
- if (oid.id != ATTESTATION_OID.id) {
- builder.addExtension(leafHolder.getExtension(oid))
- }
- }
-
- certificates.addFirst(JcaX509CertificateConverter().getCertificate(builder.build(signer)))
- return certificates.toTypedArray()
- }
-
- private fun hackSingleCertificate(leaf: X509Certificate): Certificate? {
- return try {
- val leafHolder = X509CertificateHolder(leaf.encoded)
- val extension = leafHolder.getExtension(ATTESTATION_OID)
- val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
- val encodables = sequence.toArray()
- val teeEnforced = encodables[7] as ASN1Sequence
-
- val vector = ASN1EncodableVector()
- var rootOfTrust: ASN1Encodable? = null
-
- teeEnforced.forEach { element ->
- val taggedObject = element as ASN1TaggedObject
- if (taggedObject.tagNo == 704) {
- rootOfTrust = taggedObject.baseObject.toASN1Primitive()
- } else {
- vector.add(taggedObject)
- }
- }
-
- val keybox = keyboxes[leaf.publicKey.algorithm]
- ?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
-
- val builder = X509v3CertificateBuilder(
- X509CertificateHolder(keybox.certificates[0].encoded).subject,
- leafHolder.serialNumber,
- leafHolder.notBefore,
- leafHolder.notAfter,
- leafHolder.subject,
- leafHolder.subjectPublicKeyInfo
- )
-
- val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
-
- val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
- builder.addExtension(hackedExtension)
-
- leafHolder.extensions.extensionOIDs.forEach { oid ->
- if (oid.id != ATTESTATION_OID.id) {
- builder.addExtension(leafHolder.getExtension(oid))
- }
- }
-
- JcaX509CertificateConverter().getCertificate(builder.build(signer))
- } catch (t: Throwable) {
- Logger.e("Failed to hack single certificate", t)
- null
- }
- }
-
- private fun createHackedAttestationExtension(
- originalRootOfTrust: ASN1Encodable?,
- vector: ASN1EncodableVector,
- originalEncodables: Array
- ): Extension {
- val verifiedBootKey = bootKey
- var verifiedBootHash: ByteArray? = null
-
- try {
- if (originalRootOfTrust is ASN1Sequence) {
- verifiedBootHash = getByteArrayFromAsn1(originalRootOfTrust.getObjectAt(3))
- }
- } catch (t: Throwable) {
- Logger.e("Failed to get verified boot hash from original, using generated", t)
- }
-
- if (verifiedBootHash == null) {
- verifiedBootHash = bootHash
- }
-
- val rootOfTrustElements = arrayOf(
- DEROctetString(verifiedBootKey),
- ASN1Boolean.TRUE,
- ASN1Enumerated(0),
- DEROctetString(verifiedBootHash)
- )
- val hackedRootOfTrust = DERSequence(rootOfTrustElements)
-
- vector.add(DERTaggedObject(true, 718, ASN1Integer(vendorPatchLevelLong.toLong())))
- vector.add(DERTaggedObject(true, 719, ASN1Integer(bootPatchLevelLong.toLong())))
- vector.add(DERTaggedObject(true, 706, ASN1Integer(patchLevel.toLong())))
- vector.add(DERTaggedObject(true, 705, ASN1Integer(osVersion.toLong())))
- vector.add(DERTaggedObject(704, hackedRootOfTrust))
-
- val hackEnforced = DERSequence(vector)
- originalEncodables[7] = hackEnforced
- val hackedSequence = DERSequence(originalEncodables)
- val hackedSequenceOctets = DEROctetString(hackedSequence)
-
- return Extension(ATTESTATION_OID, false, hackedSequenceOctets)
- }
-
- private fun buildCertificate(
- keyPair: KeyPair,
- keybox: KeyBox,
- params: KeyGenParameters,
- issuer: X500Name,
- uid: Int,
- signingKeyPair: KeyPair = keybox.keyPair
- ): Certificate {
- val builder = JcaX509v3CertificateBuilder(
- issuer,
- params.certificateSerial ?: BigInteger.ONE,
- params.certificateNotBefore ?: Date(),
- params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
- params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
- keyPair.public
- )
-
- builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
- builder.addExtension(createAttestationExtension(params, uid))
-
- val contentSigner = when (params.algorithm) {
- Algorithm.EC -> JcaContentSignerBuilder("SHA256withECDSA").build(signingKeyPair.private)
- Algorithm.RSA -> JcaContentSignerBuilder("SHA256withRSA").build(signingKeyPair.private)
- else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
- }
-
- return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
- }
-
- private fun createAttestationExtension(params: KeyGenParameters, uid: Int): Extension {
- try {
- val key = bootKey
- val hash = bootHash
-
- val rootOfTrustEncodables = arrayOf(
- DEROctetString(key),
- ASN1Boolean.TRUE,
- ASN1Enumerated(0),
- DEROctetString(hash)
- )
- val rootOfTrustSeq = DERSequence(rootOfTrustEncodables)
-
- val purpose = DERSet(fromIntList(params.purpose))
- val algorithm = ASN1Integer(params.algorithm.toLong())
- val keySize = ASN1Integer(params.keySize.toLong())
- val digest = DERSet(fromIntList(params.digest))
- val ecCurve = ASN1Integer(params.ecCurve.toLong())
- val noAuthRequired = DERNull.INSTANCE
-
- val osVersion = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.osVersion.toLong())
- val osPatchLevel = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.patchLevel.toLong())
- val applicationID = createApplicationId(uid)
- val bootPatchLevel = ASN1Integer(bootPatchLevelLong.toLong())
- val vendorPatchLevel = ASN1Integer(vendorPatchLevelLong.toLong())
- val creationDateTime = ASN1Integer(System.currentTimeMillis())
- val origin = ASN1Integer(0L)
- val moduleHash = DEROctetString(io.github.beakthoven.TrickyStoreOSS.moduleHash)
-
- val teeEnforcedObjects = mutableListOf(
- DERTaggedObject(true, 1, purpose),
- DERTaggedObject(true, 2, algorithm),
- DERTaggedObject(true, 3, keySize),
- DERTaggedObject(true, 5, digest),
- DERTaggedObject(true, 10, ecCurve),
- DERTaggedObject(true, 503, noAuthRequired),
- DERTaggedObject(true, 702, origin),
- DERTaggedObject(true, 704, rootOfTrustSeq),
- DERTaggedObject(true, 705, osVersion),
- DERTaggedObject(true, 706, osPatchLevel),
- DERTaggedObject(true, 718, vendorPatchLevel),
- DERTaggedObject(true, 719, bootPatchLevel),
- DERTaggedObject(true, 724, moduleHash)
- )
-
- params.brand?.let { teeEnforcedObjects.add(DERTaggedObject(true, 710, DEROctetString(it))) }
- params.device?.let { teeEnforcedObjects.add(DERTaggedObject(true, 711, DEROctetString(it))) }
- params.product?.let { teeEnforcedObjects.add(DERTaggedObject(true, 712, DEROctetString(it))) }
- params.manufacturer?.let { teeEnforcedObjects.add(DERTaggedObject(true, 716, DEROctetString(it))) }
- params.model?.let { teeEnforcedObjects.add(DERTaggedObject(true, 717, DEROctetString(it))) }
-
- params.serialno?.let { teeEnforcedObjects.add(DERTaggedObject(true, 713, DEROctetString(it))) }
- params.imei1?.let { teeEnforcedObjects.add(DERTaggedObject(true, 714, DEROctetString(it))) }
- params.imei2?.let { teeEnforcedObjects.add(DERTaggedObject(true, 715, DEROctetString(it))) }
- params.meid?.let { teeEnforcedObjects.add(DERTaggedObject(true, 723, DEROctetString(it))) }
-
- teeEnforcedObjects.sortBy { it.tagNo }
-
- val softwareEnforcedObjects = arrayOf(
- DERTaggedObject(true, 709, applicationID),
- DERTaggedObject(true, 701, creationDateTime)
- )
-
- return Extension(
- ATTESTATION_OID,
- false,
- getAsn1OctetString(teeEnforcedObjects.toTypedArray(), softwareEnforcedObjects, params)
- )
- } catch (t: Throwable) {
- Logger.e("Failed to create attestation extension", t)
- throw t
- }
- }
-
- private fun fromIntList(list: List): Array {
- return list.map { ASN1Integer(it.toLong()) }.toTypedArray()
- }
-
- private fun getAsn1OctetString(
- teeEnforcedEncodables: Array,
- softwareEnforcedEncodables: Array,
- params: KeyGenParameters
- ): ASN1OctetString {
- val attestationVersion = ASN1Integer(400L)
- val attestationSecurityLevel = ASN1Enumerated(1)
- val keymasterVersion = ASN1Integer(400L)
- val keymasterSecurityLevel = ASN1Enumerated(1)
- val attestationChallenge = DEROctetString(params.attestationChallenge ?: ByteArray(0))
- val uniqueId = DEROctetString(ByteArray(0))
- val softwareEnforced = DERSequence(softwareEnforcedEncodables)
- val teeEnforced = DERSequence(teeEnforcedEncodables)
-
- val keyDescriptionEncodables = arrayOf(
- attestationVersion,
- attestationSecurityLevel,
- keymasterVersion,
- keymasterSecurityLevel,
- attestationChallenge,
- uniqueId,
- softwareEnforced,
- teeEnforced
- )
-
- val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
- return DEROctetString(keyDescriptionSeq.encoded)
- }
-
- @Throws(Throwable::class)
- private fun createApplicationId(uid: Int): DEROctetString {
- val pm = Config.getPm() ?: throw IllegalStateException("PackageManager not found!")
- val packages = pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
-
- val packageInfoArray = Array(packages.size) { i ->
- val packageName = packages[i]
- val packageInfo = pm.getPackageInfoCompat(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
-
- DERSequence(arrayOf(
- DEROctetString(packageName.toByteArray(StandardCharsets.UTF_8)),
- ASN1Integer(packageInfo.longVersionCode)
- ))
- }
-
- val signatures = mutableSetOf()
- val messageDigest = MessageDigest.getInstance("SHA-256")
-
- packages.forEach { packageName ->
- val packageInfo = pm.getPackageInfoCompat(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
- packageInfo.signingInfo?.apkContentsSigners?.forEach { signature ->
- signatures.add(Digest(messageDigest.digest(signature.toByteArray())))
- }
- }
-
- val signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
-
- val applicationIdArray = arrayOf(
- DERSet(packageInfoArray),
- DERSet(signaturesArray)
- )
-
- return DEROctetString(DERSequence(applicationIdArray).encoded)
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS
+
+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 io.github.beakthoven.TrickyStoreOSS.*
+import io.github.beakthoven.TrickyStoreOSS.core.config.Config
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor
+import org.bouncycastle.asn1.*
+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
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
+import org.bouncycastle.util.io.pem.PemReader
+import java.io.ByteArrayInputStream
+import java.io.StringReader
+import java.math.BigInteger
+import java.nio.charset.StandardCharsets
+import java.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.*
+import java.util.concurrent.ConcurrentHashMap
+import javax.security.auth.x500.X500Principal
+
+object CertificateHacker {
+
+ private val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
+
+ private val certificateFactory: CertificateFactory by lazy {
+ try {
+ CertificateFactory.getInstance("X.509")
+ } catch (t: Throwable) {
+ Logger.e("Failed to initialize certificate factory", t)
+ throw RuntimeException("Cannot initialize certificate factory", t)
+ }
+ }
+
+ data class KeyBox(
+ val pemKeyPair: PEMKeyPair,
+ val keyPair: KeyPair,
+ val certificates: List
+ )
+
+ data class KeyIdentifier(
+ val alias: String,
+ val uid: Int
+ )
+
+ sealed class ParseResult {
+ data class Success(val data: T) : ParseResult()
+ data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
+ }
+
+ sealed class HackResult {
+ data class Success(val data: T) : HackResult()
+ data class Error(val message: String, val cause: Throwable? = null) : HackResult()
+ }
+
+ data class KeyGenParameters(
+ var keySize: Int = 0,
+ var algorithm: Int = 0,
+ var certificateSerial: BigInteger? = null,
+ var certificateNotBefore: Date? = null,
+ var certificateNotAfter: Date? = null,
+ var certificateSubject: X500Name? = null,
+ var rsaPublicExponent: BigInteger? = null,
+ var ecCurve: Int = 0,
+ var ecCurveName: String? = null,
+ var purpose: MutableList = mutableListOf(),
+ var digest: MutableList = mutableListOf(),
+ var attestationChallenge: ByteArray? = null,
+ var brand: ByteArray? = null,
+ var device: ByteArray? = null,
+ var product: ByteArray? = null,
+ var manufacturer: ByteArray? = null,
+ var model: ByteArray? = null,
+ var imei1: ByteArray? = null,
+ var imei2: ByteArray? = null,
+ var meid: ByteArray? = null,
+ var serialno: ByteArray? = null
+ ) {
+
+ constructor(params: Array) : this() {
+ parseKeyParameters(params)
+ }
+
+ private fun parseKeyParameters(params: Array) {
+ params.forEach { param ->
+ Logger.d("Processing key parameter: ${param.tag}")
+ val value = param.value
+
+ when (param.tag) {
+ Tag.KEY_SIZE -> keySize = value.integer
+ Tag.ALGORITHM -> algorithm = value.algorithm
+ Tag.CERTIFICATE_SERIAL -> certificateSerial = BigInteger(value.blob)
+ Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = Date(value.dateTime)
+ Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = Date(value.dateTime)
+ Tag.CERTIFICATE_SUBJECT -> certificateSubject = X500Name(X500Principal(value.blob).name)
+ Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
+ Tag.EC_CURVE -> {
+ ecCurve = value.ecCurve
+ ecCurveName = getEcCurveName(ecCurve)
+ }
+ Tag.PURPOSE -> purpose.add(value.keyPurpose)
+ Tag.DIGEST -> digest.add(value.digest)
+ Tag.ATTESTATION_CHALLENGE -> attestationChallenge = value.blob
+ Tag.ATTESTATION_ID_BRAND -> brand = value.blob
+ Tag.ATTESTATION_ID_DEVICE -> device = value.blob
+ Tag.ATTESTATION_ID_PRODUCT -> product = value.blob
+ Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = value.blob
+ Tag.ATTESTATION_ID_MODEL -> model = value.blob
+ Tag.ATTESTATION_ID_IMEI -> imei1 = value.blob
+ Tag.ATTESTATION_ID_SECOND_IMEI -> imei2 = value.blob
+ Tag.ATTESTATION_ID_MEID -> meid = value.blob
+ }
+ }
+ }
+
+ fun setEcCurveName(curveSize: Int) {
+ ecCurveName = when (curveSize) {
+ 224 -> "secp224r1"
+ 256 -> "secp256r1"
+ 384 -> "secp384r1"
+ 521 -> "secp521r1"
+ else -> "secp256r1"
+ }
+ }
+
+ companion object {
+ private fun getEcCurveName(curve: Int): String = when (curve) {
+ EcCurve.CURVE_25519 -> "CURVE_25519"
+ EcCurve.P_224 -> "secp224r1"
+ EcCurve.P_256 -> "secp256r1"
+ EcCurve.P_384 -> "secp384r1"
+ EcCurve.P_521 -> "secp521r1"
+ else -> throw IllegalArgumentException("Unknown EC curve: $curve")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as KeyGenParameters
+
+ return keySize == other.keySize &&
+ algorithm == other.algorithm &&
+ certificateSerial == other.certificateSerial &&
+ certificateNotBefore == other.certificateNotBefore &&
+ certificateNotAfter == other.certificateNotAfter &&
+ certificateSubject == other.certificateSubject &&
+ rsaPublicExponent == other.rsaPublicExponent &&
+ ecCurve == other.ecCurve &&
+ ecCurveName == other.ecCurveName &&
+ purpose == other.purpose &&
+ digest == other.digest &&
+ attestationChallenge.contentEquals(other.attestationChallenge) &&
+ brand.contentEquals(other.brand) &&
+ device.contentEquals(other.device) &&
+ product.contentEquals(other.product) &&
+ manufacturer.contentEquals(other.manufacturer) &&
+ model.contentEquals(other.model) &&
+ imei1.contentEquals(other.imei1) &&
+ imei2.contentEquals(other.imei2) &&
+ meid.contentEquals(other.meid) &&
+ serialno.contentEquals(other.serialno)
+ }
+
+ override fun hashCode(): Int {
+ var result = keySize
+ result = 31 * result + algorithm
+ result = 31 * result + (certificateSerial?.hashCode() ?: 0)
+ result = 31 * result + (certificateNotBefore?.hashCode() ?: 0)
+ result = 31 * result + (certificateNotAfter?.hashCode() ?: 0)
+ result = 31 * result + (certificateSubject?.hashCode() ?: 0)
+ result = 31 * result + (rsaPublicExponent?.hashCode() ?: 0)
+ result = 31 * result + ecCurve
+ result = 31 * result + (ecCurveName?.hashCode() ?: 0)
+ result = 31 * result + purpose.hashCode()
+ result = 31 * result + digest.hashCode()
+ result = 31 * result + (attestationChallenge?.contentHashCode() ?: 0)
+ result = 31 * result + (brand?.contentHashCode() ?: 0)
+ result = 31 * result + (device?.contentHashCode() ?: 0)
+ result = 31 * result + (product?.contentHashCode() ?: 0)
+ result = 31 * result + (manufacturer?.contentHashCode() ?: 0)
+ result = 31 * result + (model?.contentHashCode() ?: 0)
+ result = 31 * result + (imei1?.contentHashCode() ?: 0)
+ result = 31 * result + (imei2?.contentHashCode() ?: 0)
+ result = 31 * result + (meid?.contentHashCode() ?: 0)
+ result = 31 * result + (serialno?.contentHashCode() ?: 0)
+ return result
+ }
+ }
+
+ private val keyboxes = ConcurrentHashMap()
+ private val leafAlgorithm = ConcurrentHashMap()
+
+ private const val ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0
+ private const val ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1
+ private const val ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0
+ private const val ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1
+
+ fun canHack(): Boolean = keyboxes.isNotEmpty()
+
+ private data class Digest(val digest: ByteArray) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ other as Digest
+ return digest.contentEquals(other.digest)
+ }
+
+ override fun hashCode(): Int = digest.contentHashCode()
+ }
+
+ private fun parseKeyPair(keyContent: String): ParseResult {
+ return try {
+ PEMParser(StringReader(keyContent.trimLine())).use { parser ->
+ val pemObject = parser.readObject()
+ if (pemObject is PEMKeyPair) {
+ ParseResult.Success(pemObject)
+ } else {
+ ParseResult.Error("Invalid PEM key pair format")
+ }
+ }
+ } catch (t: Throwable) {
+ ParseResult.Error("Failed to parse PEM key pair", t)
+ }
+ }
+
+ private fun parseCertificate(certContent: String): ParseResult {
+ return try {
+ PemReader(StringReader(certContent.trimLine())).use { reader ->
+ val pemObject = reader.readPemObject()
+ val certificate = certificateFactory.generateCertificate(
+ ByteArrayInputStream(pemObject.content)
+ )
+ ParseResult.Success(certificate)
+ }
+ } catch (t: Throwable) {
+ ParseResult.Error("Failed to parse certificate", t)
+ }
+ }
+
+ @Throws(CertificateParsingException::class)
+ private fun getByteArrayFromAsn1(asn1Encodable: ASN1Encodable): ByteArray {
+ return when (asn1Encodable) {
+ is DEROctetString -> asn1Encodable.octets
+ else -> throw CertificateParsingException("Expected DEROctetString, got ${asn1Encodable::class.simpleName}")
+ }
+ }
+
+ fun readFromXml(xmlData: String?) {
+ keyboxes.clear()
+ leafAlgorithm.clear()
+
+ if (xmlData == null) {
+ Logger.i("Clearing all keyboxes")
+ return
+ }
+
+ try {
+ val xmlParser = XmlParser(xmlData)
+
+ val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
+ val numberOfKeyboxes = when (numberOfKeyboxesResult) {
+ is XmlParser.ParseResult.Success -> numberOfKeyboxesResult.attributes["text"]?.toIntOrNull()
+ ?: throw IllegalArgumentException("Invalid number of keyboxes")
+ is XmlParser.ParseResult.Error -> throw Exception(numberOfKeyboxesResult.message, numberOfKeyboxesResult.cause)
+ }
+
+ repeat(numberOfKeyboxes) { i ->
+ processKeybox(xmlParser, i)
+ }
+
+ Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
+ } catch (t: Throwable) {
+ Logger.e("Error loading XML file (keyboxes cleared)", t)
+ }
+ }
+
+ private fun processKeybox(xmlParser: XmlParser, index: Int) {
+ try {
+ val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
+ val keyboxAlgorithm = when (algorithmResult) {
+ is XmlParser.ParseResult.Success -> algorithmResult.attributes["algorithm"]
+ ?: throw IllegalArgumentException("Missing algorithm attribute")
+ is XmlParser.ParseResult.Error -> throw Exception(algorithmResult.message, algorithmResult.cause)
+ }
+
+ val privateKeyResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
+ val privateKeyContent = when (privateKeyResult) {
+ is XmlParser.ParseResult.Success -> privateKeyResult.attributes["text"]
+ ?: throw IllegalArgumentException("Missing private key text")
+ is XmlParser.ParseResult.Error -> throw Exception(privateKeyResult.message, privateKeyResult.cause)
+ }
+
+ val numberOfCertificatesResult = xmlParser.obtainPath(
+ "AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
+ )
+ val numberOfCertificates = when (numberOfCertificatesResult) {
+ is XmlParser.ParseResult.Success -> numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
+ ?: throw IllegalArgumentException("Invalid number of certificates")
+ is XmlParser.ParseResult.Error -> throw Exception(numberOfCertificatesResult.message, numberOfCertificatesResult.cause)
+ }
+
+ val certificateChain = mutableListOf()
+ repeat(numberOfCertificates) { j ->
+ val certResult = xmlParser.obtainPath(
+ "AndroidAttestation.Keybox.Key[$index].CertificateChain.Certificate[$j]"
+ )
+ val certContent = when (certResult) {
+ is XmlParser.ParseResult.Success -> certResult.attributes["text"]
+ ?: throw IllegalArgumentException("Missing certificate text")
+ is XmlParser.ParseResult.Error -> throw Exception(certResult.message, certResult.cause)
+ }
+
+ when (val certParseResult = parseCertificate(certContent)) {
+ is ParseResult.Success -> certificateChain.add(certParseResult.data)
+ is ParseResult.Error -> throw Exception(certParseResult.message, certParseResult.cause)
+ }
+ }
+
+ val pemKeyPair = when (val keyParseResult = parseKeyPair(privateKeyContent)) {
+ is ParseResult.Success -> keyParseResult.data
+ is ParseResult.Error -> throw Exception(keyParseResult.message, keyParseResult.cause)
+ }
+
+ val keyPair = JcaPEMKeyConverter().getKeyPair(pemKeyPair)
+
+ val algorithmName = when (keyboxAlgorithm.lowercase()) {
+ "ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
+ "rsa" -> KeyProperties.KEY_ALGORITHM_RSA
+ else -> keyboxAlgorithm
+ }
+
+ keyboxes[algorithmName] = KeyBox(pemKeyPair, keyPair, certificateChain)
+
+ } catch (t: Throwable) {
+ Logger.e("Error processing keybox $index", t)
+ throw t
+ }
+ }
+
+ fun hackCertificateChain(certificateChain: Array?): Array {
+ if (certificateChain == null) {
+ throw UnsupportedOperationException("Certificate chain is null!")
+ }
+
+ return try {
+ val leaf = certificateFactory.generateCertificate(
+ ByteArrayInputStream(certificateChain[0].encoded)
+ ) as X509Certificate
+
+ val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
+ ?: return certificateChain // No attestation extension, return original
+
+ hackCertificateWithAttestation(leaf, certificateChain)
+ } catch (t: Throwable) {
+ Logger.e("Failed to hack certificate chain", t)
+ certificateChain
+ }
+ }
+
+ fun hackCertificateChainCA(caList: ByteArray?, alias: String, uid: Int): ByteArray {
+ if (caList == null) {
+ throw UnsupportedOperationException("CA list is null!")
+ }
+
+ return try {
+ val key = KeyIdentifier(alias, uid)
+ val algorithm = leafAlgorithm.remove(key)
+ ?: throw UnsupportedOperationException("No algorithm found for key $key")
+
+ val keybox = keyboxes[algorithm]
+ ?: throw UnsupportedOperationException("Unsupported algorithm: $algorithm")
+
+ CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
+ } catch (t: Throwable) {
+ Logger.e("Failed to hack CA certificate chain", t)
+ caList
+ }
+ }
+
+ fun hackCertificateChainUSR(certificate: ByteArray?, alias: String, uid: Int): ByteArray {
+ if (certificate == null) {
+ throw UnsupportedOperationException("Leaf certificate is null!")
+ }
+
+ return try {
+ val leaf = certificateFactory.generateCertificate(
+ ByteArrayInputStream(certificate)
+ ) as X509Certificate
+
+ val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
+ ?: return certificate // No attestation extension, return original
+
+ val keyIdentifier = KeyIdentifier(alias, uid)
+ leafAlgorithm[keyIdentifier] = leaf.publicKey.algorithm
+
+ hackSingleCertificate(leaf)?.encoded ?: certificate
+ } catch (t: Throwable) {
+ Logger.e("Failed to hack user certificate", t)
+ certificate
+ }
+ }
+
+ fun generateKeyPair(params: KeyGenParameters): KeyPair? {
+ return try {
+ when (params.algorithm) {
+ Algorithm.EC -> {
+ Logger.d("Generating EC keypair of size ${params.keySize}")
+ buildECKeyPair(params)
+ }
+ Algorithm.RSA -> {
+ Logger.d("Generating RSA keypair of size ${params.keySize}")
+ buildRSAKeyPair(params)
+ }
+ else -> {
+ Logger.e("Unsupported algorithm: ${params.algorithm}")
+ null
+ }
+ }
+ } catch (t: Throwable) {
+ Logger.e("Failed to generate key pair", t)
+ null
+ }
+ }
+
+ fun generateChain(uid: Int, params: KeyGenParameters, keyPair: KeyPair): List? {
+ return try {
+ val keybox = getKeyboxForAlgorithm(params.algorithm)
+ ?: return null
+
+ val issuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
+ val leaf = buildCertificate(keyPair, keybox, params, issuer, uid)
+
+ val chain = mutableListOf().apply {
+ add(leaf)
+ addAll(keybox.certificates)
+ }
+
+ CertificateUtils.run { chain.toByteArrayList() }
+ } catch (t: Throwable) {
+ Logger.e("Failed to generate certificate chain", t)
+ null
+ }
+ }
+
+ fun generateKeyPair(
+ uid: Int,
+ descriptor: KeyDescriptor,
+ attestKeyDescriptor: KeyDescriptor?,
+ params: KeyGenParameters
+ ): Pair>? {
+ Logger.i("Requested KeyPair with alias: ${descriptor.alias}")
+
+ val isAttestPurpose = attestKeyDescriptor != null
+ if (isAttestPurpose) {
+ Logger.i("Requested KeyPair with attestKey: ${attestKeyDescriptor?.alias}")
+ }
+
+ return try {
+ val keyPair = generateKeyPair(params) ?: return null
+ val keybox = getKeyboxForAlgorithm(params.algorithm) ?: return null
+
+ val (rootKeyPair, issuer) = if (isAttestPurpose) {
+ val attestInfo = getAttestationKeyInfo(uid, attestKeyDescriptor!!)
+ if (attestInfo != null) {
+ attestInfo.first to attestInfo.second
+ } else {
+ keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
+ }
+ } else {
+ keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
+ }
+
+ val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, rootKeyPair)
+ val chain = if (isAttestPurpose) mutableListOf() else mutableListOf().apply { addAll(keybox.certificates) }
+ chain.add(0, leaf)
+
+ Logger.d("Successfully generated certificate for alias: ${descriptor.alias}")
+ Pair(keyPair, chain)
+ } catch (t: Throwable) {
+ Logger.e("Failed to generate key pair with certificates", t)
+ null
+ }
+ }
+
+ private fun buildECKeyPair(params: KeyGenParameters): KeyPair {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
+ Security.addProvider(BouncyCastleProvider())
+
+ val spec = ECGenParameterSpec(params.ecCurveName)
+ val keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME)
+ keyPairGenerator.initialize(spec)
+ return keyPairGenerator.generateKeyPair()
+ }
+
+ private fun buildRSAKeyPair(params: KeyGenParameters): KeyPair {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
+ Security.addProvider(BouncyCastleProvider())
+
+ val spec = RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
+ val keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
+ keyPairGenerator.initialize(spec)
+ return keyPairGenerator.generateKeyPair()
+ }
+
+ private fun getKeyboxForAlgorithm(algorithm: Int): KeyBox? {
+ val algorithmName = when (algorithm) {
+ Algorithm.EC -> KeyProperties.KEY_ALGORITHM_EC
+ Algorithm.RSA -> KeyProperties.KEY_ALGORITHM_RSA
+ else -> {
+ Logger.e("Unsupported algorithm: $algorithm")
+ return null
+ }
+ }
+ return keyboxes[algorithmName]
+ }
+
+ private fun getAttestationKeyInfo(uid: Int, attestKeyDescriptor: KeyDescriptor): Pair? {
+ Logger.d("Looking for attestation key: uid=$uid alias=${attestKeyDescriptor.alias}")
+
+ val keyInfo = SecurityLevelInterceptor.getKeyPairs(uid, attestKeyDescriptor.alias)
+ return if (keyInfo != null) {
+ val issuer = X509CertificateHolder(keyInfo.second[0].encoded).subject
+ Pair(keyInfo.first, issuer)
+ } else {
+ Logger.e("Attestation key info not found, falling back to default keybox")
+ null
+ }
+ }
+
+ private fun hackCertificateWithAttestation(leaf: X509Certificate, originalChain: Array): Array {
+ val leafHolder = X509CertificateHolder(leaf.encoded)
+ val extension = leafHolder.getExtension(ATTESTATION_OID)
+ val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
+ val encodables = sequence.toArray()
+ val teeEnforced = encodables[7] as ASN1Sequence
+
+ val vector = ASN1EncodableVector()
+ var rootOfTrust: ASN1Encodable? = null
+
+ teeEnforced.forEach { element ->
+ val taggedObject = element as ASN1TaggedObject
+ if (taggedObject.tagNo == 704) {
+ rootOfTrust = taggedObject.baseObject.toASN1Primitive()
+ } else {
+ vector.add(taggedObject)
+ }
+ }
+
+ val keybox = keyboxes[leaf.publicKey.algorithm]
+ ?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
+
+ val certificates = LinkedList(keybox.certificates)
+ val builder = X509v3CertificateBuilder(
+ X509CertificateHolder(certificates[0].encoded).subject,
+ leafHolder.serialNumber,
+ leafHolder.notBefore,
+ leafHolder.notAfter,
+ leafHolder.subject,
+ leafHolder.subjectPublicKeyInfo
+ )
+
+ val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
+
+ val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
+ builder.addExtension(hackedExtension)
+
+ leafHolder.extensions.extensionOIDs.forEach { oid ->
+ if (oid.id != ATTESTATION_OID.id) {
+ builder.addExtension(leafHolder.getExtension(oid))
+ }
+ }
+
+ certificates.addFirst(JcaX509CertificateConverter().getCertificate(builder.build(signer)))
+ return certificates.toTypedArray()
+ }
+
+ private fun hackSingleCertificate(leaf: X509Certificate): Certificate? {
+ return try {
+ val leafHolder = X509CertificateHolder(leaf.encoded)
+ val extension = leafHolder.getExtension(ATTESTATION_OID)
+ val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
+ val encodables = sequence.toArray()
+ val teeEnforced = encodables[7] as ASN1Sequence
+
+ val vector = ASN1EncodableVector()
+ var rootOfTrust: ASN1Encodable? = null
+
+ teeEnforced.forEach { element ->
+ val taggedObject = element as ASN1TaggedObject
+ if (taggedObject.tagNo == 704) {
+ rootOfTrust = taggedObject.baseObject.toASN1Primitive()
+ } else {
+ vector.add(taggedObject)
+ }
+ }
+
+ val keybox = keyboxes[leaf.publicKey.algorithm]
+ ?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
+
+ val builder = X509v3CertificateBuilder(
+ X509CertificateHolder(keybox.certificates[0].encoded).subject,
+ leafHolder.serialNumber,
+ leafHolder.notBefore,
+ leafHolder.notAfter,
+ leafHolder.subject,
+ leafHolder.subjectPublicKeyInfo
+ )
+
+ val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
+
+ val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
+ builder.addExtension(hackedExtension)
+
+ leafHolder.extensions.extensionOIDs.forEach { oid ->
+ if (oid.id != ATTESTATION_OID.id) {
+ builder.addExtension(leafHolder.getExtension(oid))
+ }
+ }
+
+ JcaX509CertificateConverter().getCertificate(builder.build(signer))
+ } catch (t: Throwable) {
+ Logger.e("Failed to hack single certificate", t)
+ null
+ }
+ }
+
+ private fun createHackedAttestationExtension(
+ originalRootOfTrust: ASN1Encodable?,
+ vector: ASN1EncodableVector,
+ originalEncodables: Array
+ ): Extension {
+ val verifiedBootKey = bootKey
+ var verifiedBootHash: ByteArray? = null
+
+ try {
+ if (originalRootOfTrust is ASN1Sequence) {
+ verifiedBootHash = getByteArrayFromAsn1(originalRootOfTrust.getObjectAt(3))
+ }
+ } catch (t: Throwable) {
+ Logger.e("Failed to get verified boot hash from original, using generated", t)
+ }
+
+ if (verifiedBootHash == null) {
+ verifiedBootHash = bootHash
+ }
+
+ val rootOfTrustElements = arrayOf(
+ DEROctetString(verifiedBootKey),
+ ASN1Boolean.TRUE,
+ ASN1Enumerated(0),
+ DEROctetString(verifiedBootHash)
+ )
+ val hackedRootOfTrust = DERSequence(rootOfTrustElements)
+
+ vector.add(DERTaggedObject(true, 718, ASN1Integer(vendorPatchLevelLong.toLong())))
+ vector.add(DERTaggedObject(true, 719, ASN1Integer(bootPatchLevelLong.toLong())))
+ vector.add(DERTaggedObject(true, 706, ASN1Integer(patchLevel.toLong())))
+ vector.add(DERTaggedObject(true, 705, ASN1Integer(osVersion.toLong())))
+ vector.add(DERTaggedObject(704, hackedRootOfTrust))
+
+ val hackEnforced = DERSequence(vector)
+ originalEncodables[7] = hackEnforced
+ val hackedSequence = DERSequence(originalEncodables)
+ val hackedSequenceOctets = DEROctetString(hackedSequence)
+
+ return Extension(ATTESTATION_OID, false, hackedSequenceOctets)
+ }
+
+ private fun buildCertificate(
+ keyPair: KeyPair,
+ keybox: KeyBox,
+ params: KeyGenParameters,
+ issuer: X500Name,
+ uid: Int,
+ signingKeyPair: KeyPair = keybox.keyPair
+ ): Certificate {
+ val builder = JcaX509v3CertificateBuilder(
+ issuer,
+ params.certificateSerial ?: BigInteger.ONE,
+ params.certificateNotBefore ?: Date(),
+ params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
+ params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
+ keyPair.public
+ )
+
+ builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
+ builder.addExtension(createAttestationExtension(params, uid))
+
+ val contentSigner = when (params.algorithm) {
+ Algorithm.EC -> JcaContentSignerBuilder("SHA256withECDSA").build(signingKeyPair.private)
+ Algorithm.RSA -> JcaContentSignerBuilder("SHA256withRSA").build(signingKeyPair.private)
+ else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
+ }
+
+ return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
+ }
+
+ private fun createAttestationExtension(params: KeyGenParameters, uid: Int): Extension {
+ try {
+ val key = bootKey
+ val hash = bootHash
+
+ val rootOfTrustEncodables = arrayOf(
+ DEROctetString(key),
+ ASN1Boolean.TRUE,
+ ASN1Enumerated(0),
+ DEROctetString(hash)
+ )
+ val rootOfTrustSeq = DERSequence(rootOfTrustEncodables)
+
+ val purpose = DERSet(fromIntList(params.purpose))
+ val algorithm = ASN1Integer(params.algorithm.toLong())
+ val keySize = ASN1Integer(params.keySize.toLong())
+ val digest = DERSet(fromIntList(params.digest))
+ val ecCurve = ASN1Integer(params.ecCurve.toLong())
+ val noAuthRequired = DERNull.INSTANCE
+
+ val osVersion = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.osVersion.toLong())
+ val osPatchLevel = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.patchLevel.toLong())
+ val applicationID = createApplicationId(uid)
+ val bootPatchLevel = ASN1Integer(bootPatchLevelLong.toLong())
+ val vendorPatchLevel = ASN1Integer(vendorPatchLevelLong.toLong())
+ val creationDateTime = ASN1Integer(System.currentTimeMillis())
+ val origin = ASN1Integer(0L)
+ val moduleHash = DEROctetString(io.github.beakthoven.TrickyStoreOSS.moduleHash)
+
+ val teeEnforcedObjects = mutableListOf(
+ DERTaggedObject(true, 1, purpose),
+ DERTaggedObject(true, 2, algorithm),
+ DERTaggedObject(true, 3, keySize),
+ DERTaggedObject(true, 5, digest),
+ DERTaggedObject(true, 10, ecCurve),
+ DERTaggedObject(true, 503, noAuthRequired),
+ DERTaggedObject(true, 702, origin),
+ DERTaggedObject(true, 704, rootOfTrustSeq),
+ DERTaggedObject(true, 705, osVersion),
+ DERTaggedObject(true, 706, osPatchLevel),
+ DERTaggedObject(true, 718, vendorPatchLevel),
+ DERTaggedObject(true, 719, bootPatchLevel),
+ DERTaggedObject(true, 724, moduleHash)
+ )
+
+ params.brand?.let { teeEnforcedObjects.add(DERTaggedObject(true, 710, DEROctetString(it))) }
+ params.device?.let { teeEnforcedObjects.add(DERTaggedObject(true, 711, DEROctetString(it))) }
+ params.product?.let { teeEnforcedObjects.add(DERTaggedObject(true, 712, DEROctetString(it))) }
+ params.manufacturer?.let { teeEnforcedObjects.add(DERTaggedObject(true, 716, DEROctetString(it))) }
+ params.model?.let { teeEnforcedObjects.add(DERTaggedObject(true, 717, DEROctetString(it))) }
+
+ params.serialno?.let { teeEnforcedObjects.add(DERTaggedObject(true, 713, DEROctetString(it))) }
+ params.imei1?.let { teeEnforcedObjects.add(DERTaggedObject(true, 714, DEROctetString(it))) }
+ params.imei2?.let { teeEnforcedObjects.add(DERTaggedObject(true, 715, DEROctetString(it))) }
+ params.meid?.let { teeEnforcedObjects.add(DERTaggedObject(true, 723, DEROctetString(it))) }
+
+ teeEnforcedObjects.sortBy { it.tagNo }
+
+ val softwareEnforcedObjects = arrayOf(
+ DERTaggedObject(true, 709, applicationID),
+ DERTaggedObject(true, 701, creationDateTime)
+ )
+
+ return Extension(
+ ATTESTATION_OID,
+ false,
+ getAsn1OctetString(teeEnforcedObjects.toTypedArray(), softwareEnforcedObjects, params)
+ )
+ } catch (t: Throwable) {
+ Logger.e("Failed to create attestation extension", t)
+ throw t
+ }
+ }
+
+ private fun fromIntList(list: List): Array {
+ return list.map { ASN1Integer(it.toLong()) }.toTypedArray()
+ }
+
+ private fun getAsn1OctetString(
+ teeEnforcedEncodables: Array,
+ softwareEnforcedEncodables: Array,
+ params: KeyGenParameters
+ ): ASN1OctetString {
+ val attestationVersion = ASN1Integer(400L)
+ val attestationSecurityLevel = ASN1Enumerated(1)
+ val keymasterVersion = ASN1Integer(400L)
+ val keymasterSecurityLevel = ASN1Enumerated(1)
+ val attestationChallenge = DEROctetString(params.attestationChallenge ?: ByteArray(0))
+ val uniqueId = DEROctetString(ByteArray(0))
+ val softwareEnforced = DERSequence(softwareEnforcedEncodables)
+ val teeEnforced = DERSequence(teeEnforcedEncodables)
+
+ val keyDescriptionEncodables = arrayOf(
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ )
+
+ val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
+ return DEROctetString(keyDescriptionSeq.encoded)
+ }
+
+ @Throws(Throwable::class)
+ private fun createApplicationId(uid: Int): DEROctetString {
+ val pm = Config.getPm() ?: throw IllegalStateException("PackageManager not found!")
+ val packages = pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
+
+ val packageInfoArray = Array(packages.size) { i ->
+ val packageName = packages[i]
+ val packageInfo = pm.getPackageInfoCompat(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
+
+ DERSequence(arrayOf(
+ DEROctetString(packageName.toByteArray(StandardCharsets.UTF_8)),
+ ASN1Integer(packageInfo.longVersionCode)
+ ))
+ }
+
+ val signatures = mutableSetOf()
+ val messageDigest = MessageDigest.getInstance("SHA-256")
+
+ packages.forEach { packageName ->
+ val packageInfo = pm.getPackageInfoCompat(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
+ packageInfo.signingInfo?.apkContentsSigners?.forEach { signature ->
+ signatures.add(Digest(messageDigest.digest(signature.toByteArray())))
+ }
+ }
+
+ val signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
+
+ val applicationIdArray = arrayOf(
+ DERSet(packageInfoArray),
+ DERSet(signaturesArray)
+ )
+
+ return DEROctetString(DERSequence(applicationIdArray).encoded)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateUtils.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateUtils.kt
index e797df1..2fbece9 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateUtils.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateUtils.kt
@@ -1,160 +1,160 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS
-
-import android.system.keystore2.KeyEntryResponse
-import android.system.keystore2.KeyMetadata
-import android.util.Log
-import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
-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
-
-object CertificateUtils {
- private const val TAG = "CertificateUtils"
-
- sealed class CertificateResult {
- data class Success(val data: T) : CertificateResult()
- data class Error(val message: String, val cause: Throwable? = null) : CertificateResult()
-
- inline fun map(transform: (T) -> R): CertificateResult = when (this) {
- is Success -> Success(transform(data))
- is Error -> this
- }
-
- fun getOrNull(): T? = when (this) {
- is Success -> data
- is Error -> null
- }
- }
-
- fun ByteArray?.toCertificate(): X509Certificate? {
- return this?.let { bytes ->
- try {
- val certFactory = CertificateFactory.getInstance("X.509")
- certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
- } catch (e: CertificateException) {
- Log.w(TAG, "Couldn't parse certificate in keystore", e)
- null
- }
- }
- }
-
- fun ByteArray.toCertificateResult(): CertificateResult {
- return try {
- val certFactory = CertificateFactory.getInstance("X.509")
- val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
- CertificateResult.Success(certificate)
- } catch (e: CertificateException) {
- CertificateResult.Error("Failed to parse certificate", e)
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- fun ByteArray?.toCertificates(): Collection {
- return this?.let { bytes ->
- try {
- val certFactory = CertificateFactory.getInstance("X.509")
- certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection
- } catch (e: CertificateException) {
- Log.w(TAG, "Couldn't parse certificates in keystore", e)
- emptyList()
- }
- } ?: emptyList()
- }
-
- fun Collection.toByteArray(): ByteArray? = runCatching {
- ByteArrayOutputStream().use { outputStream ->
- forEach { cert -> outputStream.write(cert.encoded) }
- outputStream.toByteArray()
- }
- }.onFailure {
- Log.w(TAG, "Failed to convert certificates to byte array", it)
- }.getOrNull()
-
- fun Collection.toByteArrayList(): List? = runCatching {
- map { it.encoded }
- }.onFailure {
- Log.w(TAG, "Failed to convert certificates to byte array list", it)
- }.getOrNull()
-
- fun KeyEntryResponse?.getCertificateChain(): Array? {
- val metadata = this?.metadata ?: return null
- val leafCert = metadata.certificate?.toCertificate() ?: return null
-
- return when (val chainBytes = metadata.certificateChain) {
- null -> arrayOf(leafCert)
- else -> {
- val additionalCerts = chainBytes.toCertificates()
- buildList {
- add(leafCert)
- addAll(additionalCerts)
- }.toTypedArray()
- }
- }
- }
-
- fun KeyEntryResponse.putCertificateChain(chain: Array): Result {
- return runCatching {
- metadata.putCertificateChain(chain)
- }
- }
-
- fun KeyMetadata.putCertificateChain(chain: Array): Result {
- return runCatching {
- if (chain.isEmpty()) return@runCatching
-
- certificate = chain[0].encoded
-
- if (chain.size > 1) {
- ByteArrayOutputStream().use { output ->
- for (i in 1 until chain.size) {
- output.write(chain[i].encoded)
- }
- certificateChain = output.toByteArray()
- }
- } else {
- certificateChain = null
- }
- }
- }
-}
-
-fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() }
-
-fun ByteArray?.toX509Certificates(): Collection = CertificateUtils.run { this@toX509Certificates.toCertificates() }
-
-fun Collection.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
-
-fun Collection.encodedBytesList(): List? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
-
-fun KeyEntryResponse.putCertificateChain(chain: Array): Result {
- return runCatching {
- metadata.putCertificateChain(chain).getOrThrow()
- }
-}
-
-fun KeyMetadata.putCertificateChain(chain: Array): Result {
- return runCatching {
- if (chain.isEmpty()) return@runCatching
-
- certificate = chain[0].encoded
-
- if (chain.size > 1) {
- ByteArrayOutputStream().use { output ->
- for (i in 1 until chain.size) {
- output.write(chain[i].encoded)
- }
- certificateChain = output.toByteArray()
- }
- } else {
- certificateChain = null
- }
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS
+
+import android.system.keystore2.KeyEntryResponse
+import android.system.keystore2.KeyMetadata
+import android.util.Log
+import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
+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
+
+object CertificateUtils {
+ private const val TAG = "CertificateUtils"
+
+ sealed class CertificateResult {
+ data class Success(val data: T) : CertificateResult()
+ data class Error(val message: String, val cause: Throwable? = null) : CertificateResult()
+
+ inline fun map(transform: (T) -> R): CertificateResult = when (this) {
+ is Success -> Success(transform(data))
+ is Error -> this
+ }
+
+ fun getOrNull(): T? = when (this) {
+ is Success -> data
+ is Error -> null
+ }
+ }
+
+ fun ByteArray?.toCertificate(): X509Certificate? {
+ return this?.let { bytes ->
+ try {
+ val certFactory = CertificateFactory.getInstance("X.509")
+ certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
+ } catch (e: CertificateException) {
+ Log.w(TAG, "Couldn't parse certificate in keystore", e)
+ null
+ }
+ }
+ }
+
+ fun ByteArray.toCertificateResult(): CertificateResult {
+ return try {
+ val certFactory = CertificateFactory.getInstance("X.509")
+ val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
+ CertificateResult.Success(certificate)
+ } catch (e: CertificateException) {
+ CertificateResult.Error("Failed to parse certificate", e)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun ByteArray?.toCertificates(): Collection {
+ return this?.let { bytes ->
+ try {
+ val certFactory = CertificateFactory.getInstance("X.509")
+ certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection
+ } catch (e: CertificateException) {
+ Log.w(TAG, "Couldn't parse certificates in keystore", e)
+ emptyList()
+ }
+ } ?: emptyList()
+ }
+
+ fun Collection.toByteArray(): ByteArray? = runCatching {
+ ByteArrayOutputStream().use { outputStream ->
+ forEach { cert -> outputStream.write(cert.encoded) }
+ outputStream.toByteArray()
+ }
+ }.onFailure {
+ Log.w(TAG, "Failed to convert certificates to byte array", it)
+ }.getOrNull()
+
+ fun Collection.toByteArrayList(): List? = runCatching {
+ map { it.encoded }
+ }.onFailure {
+ Log.w(TAG, "Failed to convert certificates to byte array list", it)
+ }.getOrNull()
+
+ fun KeyEntryResponse?.getCertificateChain(): Array? {
+ val metadata = this?.metadata ?: return null
+ val leafCert = metadata.certificate?.toCertificate() ?: return null
+
+ return when (val chainBytes = metadata.certificateChain) {
+ null -> arrayOf(leafCert)
+ else -> {
+ val additionalCerts = chainBytes.toCertificates()
+ buildList {
+ add(leafCert)
+ addAll(additionalCerts)
+ }.toTypedArray()
+ }
+ }
+ }
+
+ fun KeyEntryResponse.putCertificateChain(chain: Array): Result {
+ return runCatching {
+ metadata.putCertificateChain(chain)
+ }
+ }
+
+ fun KeyMetadata.putCertificateChain(chain: Array): Result {
+ return runCatching {
+ if (chain.isEmpty()) return@runCatching
+
+ certificate = chain[0].encoded
+
+ if (chain.size > 1) {
+ ByteArrayOutputStream().use { output ->
+ for (i in 1 until chain.size) {
+ output.write(chain[i].encoded)
+ }
+ certificateChain = output.toByteArray()
+ }
+ } else {
+ certificateChain = null
+ }
+ }
+ }
+}
+
+fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() }
+
+fun ByteArray?.toX509Certificates(): Collection = CertificateUtils.run { this@toX509Certificates.toCertificates() }
+
+fun Collection.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
+
+fun Collection.encodedBytesList(): List? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
+
+fun KeyEntryResponse.putCertificateChain(chain: Array): Result {
+ return runCatching {
+ metadata.putCertificateChain(chain).getOrThrow()
+ }
+}
+
+fun KeyMetadata.putCertificateChain(chain: Array): Result {
+ return runCatching {
+ if (chain.isEmpty()) return@runCatching
+
+ certificate = chain[0].encoded
+
+ if (chain.size > 1) {
+ ByteArrayOutputStream().use { output ->
+ for (i in 1 until chain.size) {
+ output.write(chain[i].encoded)
+ }
+ certificateChain = output.toByteArray()
+ }
+ } else {
+ certificateChain = null
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/XmlParser.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/XmlParser.kt
index 4d2a542..c093a75 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/XmlParser.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/XmlParser.kt
@@ -1,155 +1,155 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS
-
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-import org.xmlpull.v1.XmlPullParserFactory
-import java.io.IOException
-import java.io.StringReader
-
-class XmlParser(private val xmlContent: String) {
-
- sealed class ParseResult {
- data class Success(val attributes: Map) : ParseResult()
- data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
- }
-
- fun obtainPath(path: String): ParseResult {
- return try {
- val factory = XmlPullParserFactory.newInstance()
- val parser = factory.newPullParser()
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
- parser.setInput(StringReader(xmlContent))
-
- val tags = path.split(".").toTypedArray()
- val result = readData(parser, tags, 0, mutableMapOf())
- ParseResult.Success(result)
- } catch (e: XmlPullParserException) {
- ParseResult.Error("XML parsing error: ${e.message}", e)
- } catch (e: IOException) {
- ParseResult.Error("IO error while parsing XML: ${e.message}", e)
- } catch (e: Exception) {
- ParseResult.Error("Unexpected error: ${e.message}", e)
- }
- }
-
- @Throws(Exception::class)
- fun obtainPathLegacy(path: String): Map {
- when (val result = obtainPath(path)) {
- is ParseResult.Success -> return result.attributes
- is ParseResult.Error -> throw result.cause ?: Exception(result.message)
- }
- }
-
- @Throws(IOException::class, XmlPullParserException::class)
- private fun readData(
- parser: XmlPullParser,
- tags: Array,
- index: Int,
- tagCounts: MutableMap
- ): Map {
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- if (parser.eventType != XmlPullParser.START_TAG) {
- continue
- }
-
- val currentTag = parser.name ?: continue
- val targetTag = tags[index]
- val tagParts = targetTag.split("[")
- val baseTagName = tagParts[0]
-
- if (currentTag == baseTagName) {
- return if (tagParts.size > 1) {
- handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
- } else {
- handleRegularTag(parser, tags, index)
- }
- } else {
- skipCurrentElement(parser)
- }
- }
-
- throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
- }
-
- @Throws(IOException::class, XmlPullParserException::class)
- private fun handleIndexedTag(
- parser: XmlPullParser,
- tags: Array,
- index: Int,
- tagCounts: MutableMap,
- currentTag: String,
- indexPart: String
- ): Map {
- val targetIndex = indexPart.replace("]", "").toIntOrNull()
- ?: throw XmlPullParserException("Invalid index in tag: $indexPart")
-
- val currentCount = tagCounts.getOrDefault(currentTag, 0)
-
- return if (currentCount < targetIndex) {
- tagCounts[currentTag] = currentCount + 1
- readData(parser, tags, index, tagCounts)
- } else {
- if (index == tags.size - 1) {
- readAttributes(parser)
- } else {
- readData(parser, tags, index + 1, tagCounts)
- }
- }
- }
-
- @Throws(IOException::class, XmlPullParserException::class)
- private fun handleRegularTag(
- parser: XmlPullParser,
- tags: Array,
- index: Int
- ): Map {
- return if (index == tags.size - 1) {
- readAttributes(parser)
- } else {
- readData(parser, tags, index + 1, mutableMapOf())
- }
- }
-
- @Throws(IOException::class, XmlPullParserException::class)
- private fun readAttributes(parser: XmlPullParser): Map {
- val attributes = mutableMapOf()
-
- for (i in 0 until parser.attributeCount) {
- val name = parser.getAttributeName(i)
- val value = parser.getAttributeValue(i)
- if (name != null && value != null) {
- attributes[name] = value
- }
- }
-
- if (parser.next() == XmlPullParser.TEXT) {
- parser.text?.let { text ->
- attributes["text"] = text
- }
- }
-
- return attributes
- }
-
- @Throws(XmlPullParserException::class, IOException::class)
- private fun skipCurrentElement(parser: XmlPullParser) {
- if (parser.eventType != XmlPullParser.START_TAG) {
- throw IllegalStateException("Parser must be positioned at START_TAG")
- }
-
- var depth = 1
- while (depth != 0) {
- when (parser.next()) {
- XmlPullParser.END_TAG -> depth--
- XmlPullParser.START_TAG -> depth++
- }
- }
- }
-}
-
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS
+
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import org.xmlpull.v1.XmlPullParserFactory
+import java.io.IOException
+import java.io.StringReader
+
+class XmlParser(private val xmlContent: String) {
+
+ sealed class ParseResult {
+ data class Success(val attributes: Map) : ParseResult()
+ data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
+ }
+
+ fun obtainPath(path: String): ParseResult {
+ return try {
+ val factory = XmlPullParserFactory.newInstance()
+ val parser = factory.newPullParser()
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
+ parser.setInput(StringReader(xmlContent))
+
+ val tags = path.split(".").toTypedArray()
+ val result = readData(parser, tags, 0, mutableMapOf())
+ ParseResult.Success(result)
+ } catch (e: XmlPullParserException) {
+ ParseResult.Error("XML parsing error: ${e.message}", e)
+ } catch (e: IOException) {
+ ParseResult.Error("IO error while parsing XML: ${e.message}", e)
+ } catch (e: Exception) {
+ ParseResult.Error("Unexpected error: ${e.message}", e)
+ }
+ }
+
+ @Throws(Exception::class)
+ fun obtainPathLegacy(path: String): Map {
+ when (val result = obtainPath(path)) {
+ is ParseResult.Success -> return result.attributes
+ is ParseResult.Error -> throw result.cause ?: Exception(result.message)
+ }
+ }
+
+ @Throws(IOException::class, XmlPullParserException::class)
+ private fun readData(
+ parser: XmlPullParser,
+ tags: Array,
+ index: Int,
+ tagCounts: MutableMap
+ ): Map {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.eventType != XmlPullParser.START_TAG) {
+ continue
+ }
+
+ val currentTag = parser.name ?: continue
+ val targetTag = tags[index]
+ val tagParts = targetTag.split("[")
+ val baseTagName = tagParts[0]
+
+ if (currentTag == baseTagName) {
+ return if (tagParts.size > 1) {
+ handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
+ } else {
+ handleRegularTag(parser, tags, index)
+ }
+ } else {
+ skipCurrentElement(parser)
+ }
+ }
+
+ throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
+ }
+
+ @Throws(IOException::class, XmlPullParserException::class)
+ private fun handleIndexedTag(
+ parser: XmlPullParser,
+ tags: Array,
+ index: Int,
+ tagCounts: MutableMap,
+ currentTag: String,
+ indexPart: String
+ ): Map {
+ val targetIndex = indexPart.replace("]", "").toIntOrNull()
+ ?: throw XmlPullParserException("Invalid index in tag: $indexPart")
+
+ val currentCount = tagCounts.getOrDefault(currentTag, 0)
+
+ return if (currentCount < targetIndex) {
+ tagCounts[currentTag] = currentCount + 1
+ readData(parser, tags, index, tagCounts)
+ } else {
+ if (index == tags.size - 1) {
+ readAttributes(parser)
+ } else {
+ readData(parser, tags, index + 1, tagCounts)
+ }
+ }
+ }
+
+ @Throws(IOException::class, XmlPullParserException::class)
+ private fun handleRegularTag(
+ parser: XmlPullParser,
+ tags: Array,
+ index: Int
+ ): Map {
+ return if (index == tags.size - 1) {
+ readAttributes(parser)
+ } else {
+ readData(parser, tags, index + 1, mutableMapOf())
+ }
+ }
+
+ @Throws(IOException::class, XmlPullParserException::class)
+ private fun readAttributes(parser: XmlPullParser): Map {
+ val attributes = mutableMapOf()
+
+ for (i in 0 until parser.attributeCount) {
+ val name = parser.getAttributeName(i)
+ val value = parser.getAttributeValue(i)
+ if (name != null && value != null) {
+ attributes[name] = value
+ }
+ }
+
+ if (parser.next() == XmlPullParser.TEXT) {
+ parser.text?.let { text ->
+ attributes["text"] = text
+ }
+ }
+
+ return attributes
+ }
+
+ @Throws(XmlPullParserException::class, IOException::class)
+ private fun skipCurrentElement(parser: XmlPullParser) {
+ if (parser.eventType != XmlPullParser.START_TAG) {
+ throw IllegalStateException("Parser must be positioned at START_TAG")
+ }
+
+ var depth = 1
+ while (depth != 0) {
+ when (parser.next()) {
+ XmlPullParser.END_TAG -> depth--
+ XmlPullParser.START_TAG -> depth++
+ }
+ }
+ }
+}
+
fun String.toXmlParser(): XmlParser = XmlParser(this)
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt
index dff9f8c..ae66967 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt
@@ -1,267 +1,267 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.core.config
-
-import android.content.pm.IPackageManager
-import android.os.Build
-import android.os.FileObserver
-import android.os.ServiceManager
-import android.security.keystore.KeyGenParameterSpec
-import android.security.keystore.KeyProperties
-import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import java.io.File
-import java.security.KeyPairGenerator
-import java.security.KeyStore
-import java.security.SecureRandom
-import java.security.spec.ECGenParameterSpec
-
-object Config {
- private val hackPackages = mutableSetOf()
- private val generatePackages = mutableSetOf()
- private val packageModes = mutableMapOf()
-
- enum class Mode {
- AUTO, LEAF_HACK, GENERATE
- }
-
- private fun updateTargetPackages(f: File?) = runCatching {
- hackPackages.clear()
- generatePackages.clear()
- packageModes.clear()
- // Default: always generate for these
- listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach {
- generatePackages.add(it)
- packageModes[it] = Mode.GENERATE
- }
- f?.readLines()?.forEach {
- if (it.isNotBlank() && !it.startsWith("#")) {
- val n = it.trim()
- when {
- n.endsWith("!") -> {
- val pkg = n.removeSuffix("!").trim()
- generatePackages.add(pkg)
- packageModes[pkg] = Mode.GENERATE
- }
- n.endsWith("?") -> {
- val pkg = n.removeSuffix("?").trim()
- hackPackages.add(pkg)
- packageModes[pkg] = Mode.LEAF_HACK
- }
- else -> {
- // Auto mode
- packageModes[n] = Mode.AUTO
- }
- }
- }
- }
- Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes")
- }.onFailure {
- Logger.e("failed to update target files", it)
- }
-
- private fun updateKeyBox(f: File?) = runCatching {
- CertificateHacker.readFromXml(f?.readText())
- }.onFailure {
- Logger.e("failed to update keybox", it)
- }
-
- private const val CONFIG_PATH = "/data/adb/tricky_store"
- private const val TARGET_FILE = "target.txt"
- private const val KEYBOX_FILE = "keybox.xml"
- private const val TEE_STATUS_FILE = "tee_status"
- private const val PATCHLEVEL_FILE = "security_patch.txt"
- private val root = File(CONFIG_PATH)
-
- @Volatile
- private var teeBroken: Boolean? = null
-
- private fun isTEEWorking(): Boolean {
- val alias = "tee_attest_test_key"
- return try {
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- android.app.ActivityThread.initializeMainlineModules();
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- android.security.keystore2.AndroidKeyStoreProvider.install();
- } else {
- android.security.keystore.AndroidKeyStoreProvider.install();
- }
-
- val keyStore = KeyStore.getInstance("AndroidKeyStore")
- keyStore.load(null)
-
- val keyPairGenerator = KeyPairGenerator.getInstance(
- KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
-
- val challenge = ByteArray(16).apply {
- SecureRandom().nextBytes(this)
- }
-
- val parameterSpec = KeyGenParameterSpec.Builder(
- alias,
- KeyProperties.PURPOSE_SIGN
- )
- .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
- .setDigests(KeyProperties.DIGEST_SHA256)
- .setAttestationChallenge(challenge)
- .setIsStrongBoxBacked(false)
- .build()
-
- keyPairGenerator.initialize(parameterSpec)
- keyPairGenerator.generateKeyPair()
-
- keyStore.deleteEntry(alias)
- true
- } catch (e: Exception) {
- Logger.e("TEE check failure: ${e.message}")
- false
- }
- }
-
-
- private fun storeTEEStatus(root: File) {
- val statusFile = File(root, TEE_STATUS_FILE)
- val status = isTEEWorking()
- teeBroken = !status
- try {
- statusFile.writeText("teeBroken=${!status}")
- } catch (e: Exception) {
- Logger.e("Failed to write TEE status: ${e.message}")
- }
- }
-
- private fun loadTEEStatus(root: File) {
- val statusFile = File(root, TEE_STATUS_FILE)
- if (statusFile.exists()) {
- val line = statusFile.readText().trim()
- teeBroken = line == "teeBroken=true"
- } else {
- teeBroken = null
- }
- }
-
- object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
- override fun onEvent(event: Int, path: String?) {
- path ?: return
- val f = when (event) {
- CLOSE_WRITE, MOVED_TO -> File(root, path)
- DELETE, MOVED_FROM -> null
- else -> return
- }
- when (path) {
- TARGET_FILE -> updateTargetPackages(f)
- KEYBOX_FILE -> updateKeyBox(f)
- PATCHLEVEL_FILE -> updatePatchLevel(f)
- }
- }
- }
-
- fun initialize() {
- root.mkdirs()
- val scope = File(root, TARGET_FILE)
- if (scope.exists()) {
- updateTargetPackages(scope)
- } else {
- Logger.e("target.txt file not found, please put it to $scope !")
- }
- val keybox = File(root, KEYBOX_FILE)
- if (!keybox.exists()) {
- Logger.e("keybox file not found, please put it to $keybox !")
- } else {
- updateKeyBox(keybox)
- }
- storeTEEStatus(root)
- val patchFile = File(root, PATCHLEVEL_FILE)
- updatePatchLevel(if (patchFile.exists()) patchFile else null)
- 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): Boolean = kotlin.runCatching {
- val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
- if (teeBroken == null) loadTEEStatus(root)
- for (pkg in ps) {
- when (packageModes[pkg]) {
- Mode.LEAF_HACK -> return true
- Mode.AUTO -> {
- if (teeBroken == false) return true
- }
- else -> {}
- }
- }
- return false
- }.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
-
- fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching {
- val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
- if (teeBroken == null) loadTEEStatus(root)
- for (pkg in ps) {
- when (packageModes[pkg]) {
- Mode.GENERATE -> return true
- Mode.AUTO -> {
- if (teeBroken == true) return true
- }
- else -> {}
- }
- }
- return false
- }.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
-
- @Volatile
- var _customPatchLevel: CustomPatchLevel? = null
-
- fun updatePatchLevel(f: File?) = runCatching {
- if (f == null || !f.exists()) {
- _customPatchLevel = null
- return@runCatching
- }
- val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
- if (lines.isEmpty()) {
- _customPatchLevel = null
- return@runCatching
- }
- if (lines.size == 1 && !lines[0].contains("=")) {
- _customPatchLevel = CustomPatchLevel(all = lines[0])
- return@runCatching
- }
- val map = mutableMapOf()
- for (line in lines) {
- val idx = line.indexOf('=')
- if (idx > 0) {
- val key = line.substring(0, idx).trim().lowercase()
- val value = line.substring(idx + 1).trim()
- map[key] = value
- }
- }
- val all = map["all"]
- _customPatchLevel = CustomPatchLevel(
- system = map["system"] ?: all,
- vendor = map["vendor"] ?: all,
- boot = map["boot"] ?: all,
- all = all
- )
- }.onFailure {
- Logger.e("failed to update patch level", it)
- }
-}
-
-data class CustomPatchLevel(
- val system: String? = null,
- val vendor: String? = null,
- val boot: String? = null,
- val all: String? = null
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.core.config
+
+import android.content.pm.IPackageManager
+import android.os.Build
+import android.os.FileObserver
+import android.os.ServiceManager
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import java.io.File
+import java.security.KeyPairGenerator
+import java.security.KeyStore
+import java.security.SecureRandom
+import java.security.spec.ECGenParameterSpec
+
+object Config {
+ private val hackPackages = mutableSetOf()
+ private val generatePackages = mutableSetOf()
+ private val packageModes = mutableMapOf()
+
+ enum class Mode {
+ AUTO, LEAF_HACK, GENERATE
+ }
+
+ private fun updateTargetPackages(f: File?) = runCatching {
+ hackPackages.clear()
+ generatePackages.clear()
+ packageModes.clear()
+ // Default: always generate for these
+ listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach {
+ generatePackages.add(it)
+ packageModes[it] = Mode.GENERATE
+ }
+ f?.readLines()?.forEach {
+ if (it.isNotBlank() && !it.startsWith("#")) {
+ val n = it.trim()
+ when {
+ n.endsWith("!") -> {
+ val pkg = n.removeSuffix("!").trim()
+ generatePackages.add(pkg)
+ packageModes[pkg] = Mode.GENERATE
+ }
+ n.endsWith("?") -> {
+ val pkg = n.removeSuffix("?").trim()
+ hackPackages.add(pkg)
+ packageModes[pkg] = Mode.LEAF_HACK
+ }
+ else -> {
+ // Auto mode
+ packageModes[n] = Mode.AUTO
+ }
+ }
+ }
+ }
+ Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes")
+ }.onFailure {
+ Logger.e("failed to update target files", it)
+ }
+
+ private fun updateKeyBox(f: File?) = runCatching {
+ CertificateHacker.readFromXml(f?.readText())
+ }.onFailure {
+ Logger.e("failed to update keybox", it)
+ }
+
+ private const val CONFIG_PATH = "/data/adb/tricky_store"
+ private const val TARGET_FILE = "target.txt"
+ private const val KEYBOX_FILE = "keybox.xml"
+ private const val TEE_STATUS_FILE = "tee_status"
+ private const val PATCHLEVEL_FILE = "security_patch.txt"
+ private val root = File(CONFIG_PATH)
+
+ @Volatile
+ private var teeBroken: Boolean? = null
+
+ private fun isTEEWorking(): Boolean {
+ val alias = "tee_attest_test_key"
+ return try {
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ android.app.ActivityThread.initializeMainlineModules();
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ android.security.keystore2.AndroidKeyStoreProvider.install();
+ } else {
+ android.security.keystore.AndroidKeyStoreProvider.install();
+ }
+
+ val keyStore = KeyStore.getInstance("AndroidKeyStore")
+ keyStore.load(null)
+
+ val keyPairGenerator = KeyPairGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
+
+ val challenge = ByteArray(16).apply {
+ SecureRandom().nextBytes(this)
+ }
+
+ val parameterSpec = KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN
+ )
+ .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAttestationChallenge(challenge)
+ .setIsStrongBoxBacked(false)
+ .build()
+
+ keyPairGenerator.initialize(parameterSpec)
+ keyPairGenerator.generateKeyPair()
+
+ keyStore.deleteEntry(alias)
+ true
+ } catch (e: Exception) {
+ Logger.e("TEE check failure: ${e.message}")
+ false
+ }
+ }
+
+
+ private fun storeTEEStatus(root: File) {
+ val statusFile = File(root, TEE_STATUS_FILE)
+ val status = isTEEWorking()
+ teeBroken = !status
+ try {
+ statusFile.writeText("teeBroken=${!status}")
+ } catch (e: Exception) {
+ Logger.e("Failed to write TEE status: ${e.message}")
+ }
+ }
+
+ private fun loadTEEStatus(root: File) {
+ val statusFile = File(root, TEE_STATUS_FILE)
+ if (statusFile.exists()) {
+ val line = statusFile.readText().trim()
+ teeBroken = line == "teeBroken=true"
+ } else {
+ teeBroken = null
+ }
+ }
+
+ object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
+ override fun onEvent(event: Int, path: String?) {
+ path ?: return
+ val f = when (event) {
+ CLOSE_WRITE, MOVED_TO -> File(root, path)
+ DELETE, MOVED_FROM -> null
+ else -> return
+ }
+ when (path) {
+ TARGET_FILE -> updateTargetPackages(f)
+ KEYBOX_FILE -> updateKeyBox(f)
+ PATCHLEVEL_FILE -> updatePatchLevel(f)
+ }
+ }
+ }
+
+ fun initialize() {
+ root.mkdirs()
+ val scope = File(root, TARGET_FILE)
+ if (scope.exists()) {
+ updateTargetPackages(scope)
+ } else {
+ Logger.e("target.txt file not found, please put it to $scope !")
+ }
+ val keybox = File(root, KEYBOX_FILE)
+ if (!keybox.exists()) {
+ Logger.e("keybox file not found, please put it to $keybox !")
+ } else {
+ updateKeyBox(keybox)
+ }
+ storeTEEStatus(root)
+ val patchFile = File(root, PATCHLEVEL_FILE)
+ updatePatchLevel(if (patchFile.exists()) patchFile else null)
+ 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): Boolean = kotlin.runCatching {
+ val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
+ if (teeBroken == null) loadTEEStatus(root)
+ for (pkg in ps) {
+ when (packageModes[pkg]) {
+ Mode.LEAF_HACK -> return true
+ Mode.AUTO -> {
+ if (teeBroken == false) return true
+ }
+ else -> {}
+ }
+ }
+ return false
+ }.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
+
+ fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching {
+ val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
+ if (teeBroken == null) loadTEEStatus(root)
+ for (pkg in ps) {
+ when (packageModes[pkg]) {
+ Mode.GENERATE -> return true
+ Mode.AUTO -> {
+ if (teeBroken == true) return true
+ }
+ else -> {}
+ }
+ }
+ return false
+ }.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
+
+ @Volatile
+ var _customPatchLevel: CustomPatchLevel? = null
+
+ fun updatePatchLevel(f: File?) = runCatching {
+ if (f == null || !f.exists()) {
+ _customPatchLevel = null
+ return@runCatching
+ }
+ val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
+ if (lines.isEmpty()) {
+ _customPatchLevel = null
+ return@runCatching
+ }
+ if (lines.size == 1 && !lines[0].contains("=")) {
+ _customPatchLevel = CustomPatchLevel(all = lines[0])
+ return@runCatching
+ }
+ val map = mutableMapOf()
+ for (line in lines) {
+ val idx = line.indexOf('=')
+ if (idx > 0) {
+ val key = line.substring(0, idx).trim().lowercase()
+ val value = line.substring(idx + 1).trim()
+ map[key] = value
+ }
+ }
+ val all = map["all"]
+ _customPatchLevel = CustomPatchLevel(
+ system = map["system"] ?: all,
+ vendor = map["vendor"] ?: all,
+ boot = map["boot"] ?: all,
+ all = all
+ )
+ }.onFailure {
+ Logger.e("failed to update patch level", it)
+ }
+}
+
+data class CustomPatchLevel(
+ val system: String? = null,
+ val vendor: String? = null,
+ val boot: String? = null,
+ val all: String? = null
)
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/logging/Logger.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/logging/Logger.kt
index 3fbc53b..2fbd94c 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/logging/Logger.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/logging/Logger.kt
@@ -1,64 +1,64 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.core.logging
-
-import android.util.Log
-
-object Logger {
- const val TAG = "TrickyStore"
-
- sealed class LogLevel(val priority: Int) {
- object Debug : LogLevel(Log.DEBUG)
- object Info : LogLevel(Log.INFO)
- object Warning : LogLevel(Log.WARN)
- object Error : LogLevel(Log.ERROR)
- object Verbose : LogLevel(Log.VERBOSE)
- }
-
- fun d(message: String) {
- Log.d(TAG, message)
- }
-
- fun e(message: String) {
- Log.e(TAG, message)
- }
-
- fun e(message: String, throwable: Throwable) {
- Log.e(TAG, "wtf: $message", throwable)
- }
-
- fun i(message: String) {
- Log.i(TAG, message)
- }
-
- fun w(message: String) {
- Log.w(TAG, message)
- }
-
- fun w(message: String, throwable: Throwable) {
- Log.w(TAG, message, throwable)
- }
-
- fun v(message: String) {
- Log.v(TAG, message)
- }
-
- fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
- when (level) {
- is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
- is LogLevel.Info -> if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
- is LogLevel.Warning -> if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
- is LogLevel.Error -> if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
- is LogLevel.Verbose -> if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
- }
- }
-
- fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
- if (condition && Log.isLoggable(TAG, level.priority)) {
- log(level, messageProvider())
- }
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.core.logging
+
+import android.util.Log
+
+object Logger {
+ const val TAG = "TrickyStore"
+
+ sealed class LogLevel(val priority: Int) {
+ object Debug : LogLevel(Log.DEBUG)
+ object Info : LogLevel(Log.INFO)
+ object Warning : LogLevel(Log.WARN)
+ object Error : LogLevel(Log.ERROR)
+ object Verbose : LogLevel(Log.VERBOSE)
+ }
+
+ fun d(message: String) {
+ Log.d(TAG, message)
+ }
+
+ fun e(message: String) {
+ Log.e(TAG, message)
+ }
+
+ fun e(message: String, throwable: Throwable) {
+ Log.e(TAG, "wtf: $message", throwable)
+ }
+
+ fun i(message: String) {
+ Log.i(TAG, message)
+ }
+
+ fun w(message: String) {
+ Log.w(TAG, message)
+ }
+
+ fun w(message: String, throwable: Throwable) {
+ Log.w(TAG, message, throwable)
+ }
+
+ fun v(message: String) {
+ Log.v(TAG, message)
+ }
+
+ fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
+ when (level) {
+ is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
+ is LogLevel.Info -> if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
+ is LogLevel.Warning -> if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
+ is LogLevel.Error -> if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
+ is LogLevel.Verbose -> if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
+ }
+ }
+
+ fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
+ if (condition && Log.isLoggable(TAG, level.priority)) {
+ log(level, messageProvider())
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/BinderInterceptor.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/BinderInterceptor.kt
index fc7d910..d241b49 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/BinderInterceptor.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/BinderInterceptor.kt
@@ -1,181 +1,181 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.interceptors
-
-import android.os.Binder
-import android.os.IBinder
-import android.os.Parcel
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-
-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()
-
- companion object {
- private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
-
- private const val REGISTER_INTERCEPTOR_CODE = 1
-
- private const val PRE_TRANSACT_CODE = 1
- private const val POST_TRANSACT_CODE = 2
-
- private const val RESULT_SKIP = 1
- private const val RESULT_CONTINUE = 2
- private const val RESULT_OVERRIDE_REPLY = 3
- private const val RESULT_OVERRIDE_DATA = 4
-
- fun getBinderBackdoor(binder: IBinder): IBinder? {
- val data = Parcel.obtain()
- val reply = Parcel.obtain()
-
- return try {
- val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
- if (success) {
- Logger.d("Backdoor access granted for binder: $binder")
- reply.readStrongBinder()
- } else {
- Logger.d("Backdoor access denied for binder: $binder")
- null
- }
- } catch (e: Exception) {
- Logger.e("Failed to access binder backdoor", e)
- null
- } finally {
- data.recycle()
- reply.recycle()
- }
- }
-
- fun registerBinderInterceptor(
- backdoor: IBinder,
- target: IBinder,
- interceptor: BinderInterceptor
- ) {
- val data = Parcel.obtain()
- val reply = Parcel.obtain()
-
- try {
- data.writeStrongBinder(target)
- data.writeStrongBinder(interceptor)
- backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
- Logger.d("Registered interceptor for target: $target")
- } catch (e: Exception) {
- Logger.e("Failed to register binder interceptor", e)
- } finally {
- data.recycle()
- reply.recycle()
- }
- }
- }
-
- 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) {
- PRE_TRANSACT_CODE -> handlePreTransact(data)
- POST_TRANSACT_CODE -> handlePostTransact(data)
- else -> return super.onTransact(code, data, reply, flags)
- }
-
- writeResultToReply(result, reply!!)
- return true
- }
-
- private fun handlePreTransact(data: Parcel): Result {
- val target = data.readStrongBinder()
- val transactionCode = data.readInt()
- val transactionFlags = data.readInt()
- val callingUid = data.readInt()
- val callingPid = data.readInt()
- val dataSize = data.readLong()
-
- val transactionData = Parcel.obtain()
- return try {
- transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
- transactionData.setDataPosition(0)
- onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
- } finally {
- transactionData.recycle()
- }
- }
-
- private fun handlePostTransact(data: Parcel): Result {
- val target = data.readStrongBinder()
- val transactionCode = data.readInt()
- val transactionFlags = data.readInt()
- val callingUid = data.readInt()
- val callingPid = data.readInt()
- val resultCode = data.readInt()
-
- val transactionData = Parcel.obtain()
- val transactionReply = Parcel.obtain()
-
- return try {
- val dataSize = data.readLong().toInt()
- transactionData.appendFrom(data, data.dataPosition(), dataSize)
- transactionData.setDataPosition(0)
- data.setDataPosition(data.dataPosition() + dataSize)
-
- val replySize = data.readLong().toInt()
- val reply = if (replySize > 0) {
- transactionReply.appendFrom(data, data.dataPosition(), replySize)
- transactionReply.setDataPosition(0)
- transactionReply
- } else null
-
- onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
- } finally {
- transactionData.recycle()
- transactionReply.recycle()
- }
- }
-
- private fun writeResultToReply(result: Result, reply: Parcel) {
- when (result) {
- Skip -> reply.writeInt(RESULT_SKIP)
- Continue -> reply.writeInt(RESULT_CONTINUE)
- is OverrideReply -> {
- reply.writeInt(RESULT_OVERRIDE_REPLY)
- reply.writeInt(result.code)
- reply.writeLong(result.reply.dataSize().toLong())
- reply.appendFrom(result.reply, 0, result.reply.dataSize())
- result.reply.recycle()
- }
- is OverrideData -> {
- reply.writeInt(RESULT_OVERRIDE_DATA)
- reply.writeLong(result.data.dataSize().toLong())
- reply.appendFrom(result.data, 0, result.data.dataSize())
- result.data.recycle()
- }
- }
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.interceptors
+
+import android.os.Binder
+import android.os.IBinder
+import android.os.Parcel
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+
+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()
+
+ companion object {
+ private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
+
+ private const val REGISTER_INTERCEPTOR_CODE = 1
+
+ private const val PRE_TRANSACT_CODE = 1
+ private const val POST_TRANSACT_CODE = 2
+
+ private const val RESULT_SKIP = 1
+ private const val RESULT_CONTINUE = 2
+ private const val RESULT_OVERRIDE_REPLY = 3
+ private const val RESULT_OVERRIDE_DATA = 4
+
+ fun getBinderBackdoor(binder: IBinder): IBinder? {
+ val data = Parcel.obtain()
+ val reply = Parcel.obtain()
+
+ return try {
+ val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
+ if (success) {
+ Logger.d("Backdoor access granted for binder: $binder")
+ reply.readStrongBinder()
+ } else {
+ Logger.d("Backdoor access denied for binder: $binder")
+ null
+ }
+ } catch (e: Exception) {
+ Logger.e("Failed to access binder backdoor", e)
+ null
+ } finally {
+ data.recycle()
+ reply.recycle()
+ }
+ }
+
+ fun registerBinderInterceptor(
+ backdoor: IBinder,
+ target: IBinder,
+ interceptor: BinderInterceptor
+ ) {
+ val data = Parcel.obtain()
+ val reply = Parcel.obtain()
+
+ try {
+ data.writeStrongBinder(target)
+ data.writeStrongBinder(interceptor)
+ backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
+ Logger.d("Registered interceptor for target: $target")
+ } catch (e: Exception) {
+ Logger.e("Failed to register binder interceptor", e)
+ } finally {
+ data.recycle()
+ reply.recycle()
+ }
+ }
+ }
+
+ 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) {
+ PRE_TRANSACT_CODE -> handlePreTransact(data)
+ POST_TRANSACT_CODE -> handlePostTransact(data)
+ else -> return super.onTransact(code, data, reply, flags)
+ }
+
+ writeResultToReply(result, reply!!)
+ return true
+ }
+
+ private fun handlePreTransact(data: Parcel): Result {
+ val target = data.readStrongBinder()
+ val transactionCode = data.readInt()
+ val transactionFlags = data.readInt()
+ val callingUid = data.readInt()
+ val callingPid = data.readInt()
+ val dataSize = data.readLong()
+
+ val transactionData = Parcel.obtain()
+ return try {
+ transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
+ transactionData.setDataPosition(0)
+ onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
+ } finally {
+ transactionData.recycle()
+ }
+ }
+
+ private fun handlePostTransact(data: Parcel): Result {
+ val target = data.readStrongBinder()
+ val transactionCode = data.readInt()
+ val transactionFlags = data.readInt()
+ val callingUid = data.readInt()
+ val callingPid = data.readInt()
+ val resultCode = data.readInt()
+
+ val transactionData = Parcel.obtain()
+ val transactionReply = Parcel.obtain()
+
+ return try {
+ val dataSize = data.readLong().toInt()
+ transactionData.appendFrom(data, data.dataPosition(), dataSize)
+ transactionData.setDataPosition(0)
+ data.setDataPosition(data.dataPosition() + dataSize)
+
+ val replySize = data.readLong().toInt()
+ val reply = if (replySize > 0) {
+ transactionReply.appendFrom(data, data.dataPosition(), replySize)
+ transactionReply.setDataPosition(0)
+ transactionReply
+ } else null
+
+ onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
+ } finally {
+ transactionData.recycle()
+ transactionReply.recycle()
+ }
+ }
+
+ private fun writeResultToReply(result: Result, reply: Parcel) {
+ when (result) {
+ Skip -> reply.writeInt(RESULT_SKIP)
+ Continue -> reply.writeInt(RESULT_CONTINUE)
+ is OverrideReply -> {
+ reply.writeInt(RESULT_OVERRIDE_REPLY)
+ reply.writeInt(result.code)
+ reply.writeLong(result.reply.dataSize().toLong())
+ reply.appendFrom(result.reply, 0, result.reply.dataSize())
+ result.reply.recycle()
+ }
+ is OverrideData -> {
+ reply.writeInt(RESULT_OVERRIDE_DATA)
+ reply.writeLong(result.data.dataSize().toLong())
+ reply.appendFrom(result.data, 0, result.data.dataSize())
+ result.data.recycle()
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/InterceptorUtils.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/InterceptorUtils.kt
index d0600b2..e07299b 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/InterceptorUtils.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/InterceptorUtils.kt
@@ -1,142 +1,142 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.interceptors
-
-import android.os.IBinder
-import android.os.Parcel
-import android.os.Parcelable
-import android.os.ServiceManager
-import android.security.KeyStore
-import android.security.keystore.KeystoreResponse
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import kotlin.system.exitProcess
-
-abstract class BaseKeystoreInterceptor : BinderInterceptor() {
-
- protected lateinit var keystore: IBinder
- protected var triedCount = 0
- protected var injected = false
- protected open val maxRetries: Int = 3
-
- protected abstract val serviceName: String
- protected abstract val injectionCommand: String
- protected abstract val processName: String
-
- fun tryRunKeystoreInterceptor(): Boolean {
- Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
-
- val service = getService() ?: return false
- val backdoor = getBinderBackdoor(service)
-
- return if (backdoor != null) {
- setupInterceptor(service, backdoor)
- } else {
- handleMissingBackdoor()
- }
- }
-
- protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
-
- protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
- keystore = service
- Logger.i("Registering for $serviceName: $keystore")
-
- registerBinderInterceptor(backdoor, service, this)
- service.linkToDeath(createDeathRecipient(), 0)
- onInterceptorSetup(service, backdoor)
-
- return true
- }
-
- private fun handleMissingBackdoor(): Boolean {
- if (triedCount >= maxRetries) {
- Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
- exitProcess(1)
- }
-
- if (!injected) {
- performInjection()
- injected = true
- }
-
- triedCount++
- return false
- }
-
- protected open fun performInjection() {
- Logger.i("Attempting to inject into $processName...")
-
- val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
- Logger.d("Injection command: ${command.joinToString(" ")}")
-
- val process = Runtime.getRuntime().exec(command)
-
- if (process.waitFor() != 0) {
- Logger.e("Injection failed! Daemon will exit")
- exitProcess(1)
- }
-
- Logger.i("Injection completed successfully")
- }
-
- protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
- override fun binderDied() {
- Logger.d("$serviceName died, daemon restarting")
- exitProcess(0)
- }
- }
-
- protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
- // Default implementation does nothing
- }
-}
-
-object InterceptorUtils {
-
- fun createSuccessKeystoreResponse(): KeystoreResponse {
- val parcel = Parcel.obtain()
- try {
- parcel.writeInt(KeyStore.NO_ERROR)
- parcel.writeString("")
- parcel.setDataPosition(0)
- return KeystoreResponse.CREATOR.createFromParcel(parcel)
- } finally {
- parcel.recycle()
- }
- }
-
- fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
- val parcel = Parcel.obtain()
- parcel.writeNoException()
- parcel.writeInt(resultCode)
- return BinderInterceptor.OverrideReply(0, parcel)
- }
-
- fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
- val parcel = Parcel.obtain()
- parcel.writeNoException()
- parcel.writeByteArray(data)
- return BinderInterceptor.OverrideReply(resultCode, parcel)
- }
-
- fun createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
- val parcel = Parcel.obtain()
- parcel.writeNoException()
- parcel.writeTypedObject(obj, flags)
- return BinderInterceptor.OverrideReply(resultCode, parcel)
- }
-
- fun String.extractAlias(): String {
- return when {
- contains("_") -> split("_")[1]
- else -> this
- }
- }
-
- fun Parcel.hasException(): Boolean {
- return kotlin.runCatching { readException() }.exceptionOrNull() != null
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.interceptors
+
+import android.os.IBinder
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.ServiceManager
+import android.security.KeyStore
+import android.security.keystore.KeystoreResponse
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import kotlin.system.exitProcess
+
+abstract class BaseKeystoreInterceptor : BinderInterceptor() {
+
+ protected lateinit var keystore: IBinder
+ protected var triedCount = 0
+ protected var injected = false
+ protected open val maxRetries: Int = 3
+
+ protected abstract val serviceName: String
+ protected abstract val injectionCommand: String
+ protected abstract val processName: String
+
+ fun tryRunKeystoreInterceptor(): Boolean {
+ Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
+
+ val service = getService() ?: return false
+ val backdoor = getBinderBackdoor(service)
+
+ return if (backdoor != null) {
+ setupInterceptor(service, backdoor)
+ } else {
+ handleMissingBackdoor()
+ }
+ }
+
+ protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
+
+ protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
+ keystore = service
+ Logger.i("Registering for $serviceName: $keystore")
+
+ registerBinderInterceptor(backdoor, service, this)
+ service.linkToDeath(createDeathRecipient(), 0)
+ onInterceptorSetup(service, backdoor)
+
+ return true
+ }
+
+ private fun handleMissingBackdoor(): Boolean {
+ if (triedCount >= maxRetries) {
+ Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
+ exitProcess(1)
+ }
+
+ if (!injected) {
+ performInjection()
+ injected = true
+ }
+
+ triedCount++
+ return false
+ }
+
+ protected open fun performInjection() {
+ Logger.i("Attempting to inject into $processName...")
+
+ val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
+ Logger.d("Injection command: ${command.joinToString(" ")}")
+
+ val process = Runtime.getRuntime().exec(command)
+
+ if (process.waitFor() != 0) {
+ Logger.e("Injection failed! Daemon will exit")
+ exitProcess(1)
+ }
+
+ Logger.i("Injection completed successfully")
+ }
+
+ protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
+ override fun binderDied() {
+ Logger.d("$serviceName died, daemon restarting")
+ exitProcess(0)
+ }
+ }
+
+ protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
+ // Default implementation does nothing
+ }
+}
+
+object InterceptorUtils {
+
+ fun createSuccessKeystoreResponse(): KeystoreResponse {
+ val parcel = Parcel.obtain()
+ try {
+ parcel.writeInt(KeyStore.NO_ERROR)
+ parcel.writeString("")
+ parcel.setDataPosition(0)
+ return KeystoreResponse.CREATOR.createFromParcel(parcel)
+ } finally {
+ parcel.recycle()
+ }
+ }
+
+ fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
+ val parcel = Parcel.obtain()
+ parcel.writeNoException()
+ parcel.writeInt(resultCode)
+ return BinderInterceptor.OverrideReply(0, parcel)
+ }
+
+ fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
+ val parcel = Parcel.obtain()
+ parcel.writeNoException()
+ parcel.writeByteArray(data)
+ return BinderInterceptor.OverrideReply(resultCode, parcel)
+ }
+
+ fun createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
+ val parcel = Parcel.obtain()
+ parcel.writeNoException()
+ parcel.writeTypedObject(obj, flags)
+ return BinderInterceptor.OverrideReply(resultCode, parcel)
+ }
+
+ fun String.extractAlias(): String {
+ return when {
+ contains("_") -> split("_")[1]
+ else -> this
+ }
+ }
+
+ fun Parcel.hasException(): Boolean {
+ return kotlin.runCatching { readException() }.exceptionOrNull() != null
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/Keystore2Interceptor.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/Keystore2Interceptor.kt
index 1d7aead..a21016f 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/Keystore2Interceptor.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/Keystore2Interceptor.kt
@@ -1,158 +1,158 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.interceptors
-
-import android.annotation.SuppressLint
-import android.hardware.security.keymint.SecurityLevel
-import android.os.IBinder
-import android.os.Parcel
-import android.system.keystore2.IKeystoreService
-import android.system.keystore2.KeyDescriptor
-import android.system.keystore2.KeyEntryResponse
-import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
-import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
-import io.github.beakthoven.TrickyStoreOSS.core.config.Config
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import io.github.beakthoven.TrickyStoreOSS.getTransactCode
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
-import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
-
-@SuppressLint("BlockedPrivateApi")
-object Keystore2Interceptor : BaseKeystoreInterceptor() {
- private val getKeyEntryTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
- private val deleteKeyTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
-
- override val serviceName = "android.system.keystore2.IKeystoreService/default"
- override val processName = "keystore2"
- override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
-
- private var teeInterceptor: SecurityLevelInterceptor? = null
- private var strongBoxInterceptor: SecurityLevelInterceptor? = null
-
- override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
- setupSecurityLevelInterceptors(service, backdoor)
- }
-
- private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
- val ks = IKeystoreService.Stub.asInterface(service)
-
- val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
- .getOrNull()
- if (tee != null) {
- Logger.i("Registering for TEE SecurityLevel: $tee")
- val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
- registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
- teeInterceptor = interceptor
- } else {
- Logger.i("No TEE SecurityLevel found")
- }
-
- val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
- .getOrNull()
- if (strongBox != null) {
- Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
- val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
- registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
- strongBoxInterceptor = interceptor
- } else {
- Logger.i("No StrongBox SecurityLevel found")
- }
- }
-
- override fun onPreTransact(
- target: IBinder,
- code: Int,
- flags: Int,
- callingUid: Int,
- callingPid: Int,
- data: Parcel
- ): Result {
- if (code == getKeyEntryTransaction) {
- if (CertificateHacker.canHack()) {
- Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
- kotlin.runCatching {
- data.enforceInterface(IKeystoreService.DESCRIPTOR)
- val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
- if (Config.needGenerate(callingUid)) {
- val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
- ?: return@runCatching
- Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
- return createTypedObjectReply(response)
- } else if (Config.needHack(callingUid)) {
- if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
- Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
- val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
- if (response != null) {
- Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}")
- return createTypedObjectReply(response)
- } else {
- Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}")
- return@runCatching
- }
- } else {
- Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}")
- return Continue
- }
- }
- return Skip
- }
- }
- }
- return Skip
- }
-
- override fun onPostTransact(
- target: IBinder,
- code: Int,
- flags: Int,
- callingUid: Int,
- callingPid: Int,
- data: Parcel,
- reply: Parcel?,
- resultCode: Int
- ): Result {
- if (target != keystore || reply == null) return Skip
- if (reply.hasException()) return Skip
- val p = Parcel.obtain()
- Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
-
- if (code == deleteKeyTransaction && resultCode == 0) {
- data.enforceInterface("android.system.keystore2.IKeystoreService")
-
- val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
- if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
-
- SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias))
-
- return Skip
- } else if (code == getKeyEntryTransaction) {
- try {
- data.enforceInterface("android.system.keystore2.IKeystoreService")
- val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
- if (response != null) {
- val chain = CertificateUtils.run { response.getCertificateChain() }
- if (chain != null) {
- val newChain = CertificateHacker.hackCertificateChain(chain)
- response.putCertificateChain(newChain).getOrThrow()
- Logger.i("Hacked certificate for uid=$callingUid")
- return createTypedObjectReply(response)
- } else {
- p.recycle()
- }
- } else {
- p.recycle()
- }
- } catch (t: Throwable) {
- Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
- p.recycle()
- }
- }
- return Skip
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.interceptors
+
+import android.annotation.SuppressLint
+import android.hardware.security.keymint.SecurityLevel
+import android.os.IBinder
+import android.os.Parcel
+import android.system.keystore2.IKeystoreService
+import android.system.keystore2.KeyDescriptor
+import android.system.keystore2.KeyEntryResponse
+import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
+import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
+import io.github.beakthoven.TrickyStoreOSS.core.config.Config
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import io.github.beakthoven.TrickyStoreOSS.getTransactCode
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
+import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
+
+@SuppressLint("BlockedPrivateApi")
+object Keystore2Interceptor : BaseKeystoreInterceptor() {
+ private val getKeyEntryTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
+ private val deleteKeyTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
+
+ override val serviceName = "android.system.keystore2.IKeystoreService/default"
+ override val processName = "keystore2"
+ override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
+
+ private var teeInterceptor: SecurityLevelInterceptor? = null
+ private var strongBoxInterceptor: SecurityLevelInterceptor? = null
+
+ override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
+ setupSecurityLevelInterceptors(service, backdoor)
+ }
+
+ private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
+ val ks = IKeystoreService.Stub.asInterface(service)
+
+ val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
+ .getOrNull()
+ if (tee != null) {
+ Logger.i("Registering for TEE SecurityLevel: $tee")
+ val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
+ registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
+ teeInterceptor = interceptor
+ } else {
+ Logger.i("No TEE SecurityLevel found")
+ }
+
+ val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
+ .getOrNull()
+ if (strongBox != null) {
+ Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
+ val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
+ registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
+ strongBoxInterceptor = interceptor
+ } else {
+ Logger.i("No StrongBox SecurityLevel found")
+ }
+ }
+
+ override fun onPreTransact(
+ target: IBinder,
+ code: Int,
+ flags: Int,
+ callingUid: Int,
+ callingPid: Int,
+ data: Parcel
+ ): Result {
+ if (code == getKeyEntryTransaction) {
+ if (CertificateHacker.canHack()) {
+ Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
+ kotlin.runCatching {
+ data.enforceInterface(IKeystoreService.DESCRIPTOR)
+ val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
+ if (Config.needGenerate(callingUid)) {
+ val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
+ ?: return@runCatching
+ Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
+ return createTypedObjectReply(response)
+ } else if (Config.needHack(callingUid)) {
+ if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
+ Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
+ val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
+ if (response != null) {
+ Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}")
+ return createTypedObjectReply(response)
+ } else {
+ Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}")
+ return@runCatching
+ }
+ } else {
+ Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}")
+ return Continue
+ }
+ }
+ return Skip
+ }
+ }
+ }
+ return Skip
+ }
+
+ override fun onPostTransact(
+ target: IBinder,
+ code: Int,
+ flags: Int,
+ callingUid: Int,
+ callingPid: Int,
+ data: Parcel,
+ reply: Parcel?,
+ resultCode: Int
+ ): Result {
+ if (target != keystore || reply == null) return Skip
+ if (reply.hasException()) return Skip
+ val p = Parcel.obtain()
+ Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
+
+ if (code == deleteKeyTransaction && resultCode == 0) {
+ data.enforceInterface("android.system.keystore2.IKeystoreService")
+
+ val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
+ if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
+
+ SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias))
+
+ return Skip
+ } else if (code == getKeyEntryTransaction) {
+ try {
+ data.enforceInterface("android.system.keystore2.IKeystoreService")
+ val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
+ if (response != null) {
+ val chain = CertificateUtils.run { response.getCertificateChain() }
+ if (chain != null) {
+ val newChain = CertificateHacker.hackCertificateChain(chain)
+ response.putCertificateChain(newChain).getOrThrow()
+ Logger.i("Hacked certificate for uid=$callingUid")
+ return createTypedObjectReply(response)
+ } else {
+ p.recycle()
+ }
+ } else {
+ p.recycle()
+ }
+ } catch (t: Throwable) {
+ Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
+ p.recycle()
+ }
+ }
+ return Skip
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/KeystoreInterceptor.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/KeystoreInterceptor.kt
index 45e3e33..0aa15c7 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/KeystoreInterceptor.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/KeystoreInterceptor.kt
@@ -1,238 +1,238 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.interceptors
-
-import android.annotation.SuppressLint
-import android.os.IBinder
-import android.os.Parcel
-import android.security.Credentials
-import android.security.KeyStore
-import android.security.keymaster.ExportResult
-import android.security.keymaster.KeyCharacteristics
-import android.security.keymaster.KeymasterArguments
-import android.security.keymaster.KeymasterCertificateChain
-import android.security.keymaster.KeymasterDefs
-import android.security.keystore.IKeystoreCertificateChainCallback
-import android.security.keystore.IKeystoreExportKeyCallback
-import android.security.keystore.IKeystoreKeyCharacteristicsCallback
-import android.security.keystore.IKeystoreService
-import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
-import io.github.beakthoven.TrickyStoreOSS.core.config.Config
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import io.github.beakthoven.TrickyStoreOSS.getTransactCode
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
-import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
-import java.math.BigInteger
-import java.security.KeyPair
-import java.util.Date
-
-@SuppressLint("BlockedPrivateApi")
-object KeystoreInterceptor : BaseKeystoreInterceptor() {
- private val getTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "get")
- private val generateKeyTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
- private val getKeyCharacteristicsTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
- private val exportKeyTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
- private val attestKeyTransaction =
- getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
-
- override val serviceName = "android.security.keystore"
- override val processName = "keystore"
- override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
-
- private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
-
- private val keyArguments = HashMap()
- private val keyPairs = HashMap()
-
- data class Key(val uid: Int, val alias: String)
-
- override fun onPreTransact(
- target: IBinder,
- code: Int,
- flags: Int,
- callingUid: Int,
- callingPid: Int,
- data: Parcel
- ): Result {
- if (CertificateHacker.canHack()) {
- if (code == getTransaction) {
- if (Config.needHack(callingUid)) {
- return Continue
- } else if (Config.needGenerate(callingUid)) {
- return Skip
- }
- } else if (Config.needGenerate(callingUid)) {
- when (code) {
- generateKeyTransaction -> {
- kotlin.runCatching {
- data.enforceInterface(DESCRIPTOR)
- val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
- val alias = data.readString()!!.extractAlias()
- Logger.i("generateKeyTransaction uid $callingUid alias $alias")
- val check = data.readInt()
- val kma = KeymasterArguments()
- val kgp = CertificateHacker.KeyGenParameters()
- if (check == 1) {
- kma.readFromParcel(data)
- kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
- kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
- kgp.setEcCurveName(kgp.keySize)
- kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
- kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
- kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
- if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
- try {
- val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
- getArgumentByTag.isAccessible = true
- val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
-
- val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
- getLongTagValue.isAccessible = true
- kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
- } catch (ex: Exception) {
- Logger.e("Read rsaPublicExponent error", ex)
- }
- }
- keyArguments[Key(callingUid, alias)] = kgp
- }
-
- val kc = KeyCharacteristics()
- kc.swEnforced = KeymasterArguments()
- kc.hwEnforced = kma
-
- val ksr = createSuccessKeystoreResponse()
- callback.onFinished(ksr, kc)
-
- return createSuccessReply()
- }.onFailure {
- Logger.e("generateKeyTransaction error", it)
- }
- }
-
- getKeyCharacteristicsTransaction -> {
- kotlin.runCatching {
- data.enforceInterface(DESCRIPTOR)
- val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
- val alias = data.readString()!!.extractAlias()
- Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
- val kc = KeyCharacteristics()
- val kma = KeymasterArguments()
- kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
- kc.swEnforced = KeymasterArguments()
- kc.hwEnforced = kma
-
- val ksr = createSuccessKeystoreResponse()
- callback.onFinished(ksr, kc)
-
- return createSuccessReply()
- }.onFailure {
- Logger.e("getKeyCharacteristicsTransaction error", it)
- }
- }
-
- exportKeyTransaction -> {
- kotlin.runCatching {
- data.enforceInterface(DESCRIPTOR)
- val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
- val alias = data.readString()!!.extractAlias()
- Logger.i("exportKeyTransaction uid $callingUid alias $alias")
- val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
- keyPairs[Key(callingUid, alias)] = kp!!
-
- val erP = Parcel.obtain()
- erP.writeInt(KeyStore.NO_ERROR)
- erP.writeByteArray(kp.public.encoded)
- erP.setDataPosition(0)
- val er = ExportResult.CREATOR.createFromParcel(erP)
- erP.recycle()
-
- callback.onFinished(er)
-
- return createSuccessReply()
- }.onFailure {
- Logger.e("exportKeyTransaction error", it)
- }
- }
-
- attestKeyTransaction -> {
- kotlin.runCatching {
- data.enforceInterface(DESCRIPTOR)
- val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
- val alias = data.readString()!!.extractAlias()
- Logger.i("attestKeyTransaction uid $callingUid alias $alias")
- val check = data.readInt()
- val kma = KeymasterArguments()
- if (check == 1) {
- kma.readFromParcel(data)
- val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
-
- val ksr = createSuccessKeystoreResponse()
-
- val key = Key(callingUid, alias)
- val ka = keyArguments[key]!!
- ka.attestationChallenge = attestationChallenge
- val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
-
- val kcc = KeymasterCertificateChain(chain)
- callback.onFinished(ksr, kcc)
- }
-
- return createSuccessReply()
- }.onFailure {
- Logger.e("attestKeyTransaction error", it)
- }
- }
- }
- }
- }
- return Skip
- }
-
- override fun onPostTransact(
- target: IBinder,
- code: Int,
- flags: Int,
- callingUid: Int,
- callingPid: Int,
- data: Parcel,
- reply: Parcel?,
- resultCode: Int
- ): Result {
- if (target != keystore || code != getTransaction || reply == null) return Skip
- if (reply.hasException()) return Skip
- val p = Parcel.obtain()
- Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
- try {
- data.enforceInterface(DESCRIPTOR)
- val alias = data.readString() ?: ""
- var response = reply.createByteArray()
- when {
- alias.startsWith(Credentials.USER_CERTIFICATE) -> {
- response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid)
- Logger.i("Hacked leaf certificate for uid=$callingUid")
- return createByteArrayReply(response)
- }
- alias.startsWith(Credentials.CA_CERTIFICATE) -> {
- response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid)
- Logger.i("Hacked CA certificate chain for uid=$callingUid")
- return createByteArrayReply(response)
- }
- else -> p.recycle()
- }
- } catch (t: Throwable) {
- Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
- p.recycle()
- }
- return Skip
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.interceptors
+
+import android.annotation.SuppressLint
+import android.os.IBinder
+import android.os.Parcel
+import android.security.Credentials
+import android.security.KeyStore
+import android.security.keymaster.ExportResult
+import android.security.keymaster.KeyCharacteristics
+import android.security.keymaster.KeymasterArguments
+import android.security.keymaster.KeymasterCertificateChain
+import android.security.keymaster.KeymasterDefs
+import android.security.keystore.IKeystoreCertificateChainCallback
+import android.security.keystore.IKeystoreExportKeyCallback
+import android.security.keystore.IKeystoreKeyCharacteristicsCallback
+import android.security.keystore.IKeystoreService
+import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
+import io.github.beakthoven.TrickyStoreOSS.core.config.Config
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import io.github.beakthoven.TrickyStoreOSS.getTransactCode
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
+import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
+import java.math.BigInteger
+import java.security.KeyPair
+import java.util.Date
+
+@SuppressLint("BlockedPrivateApi")
+object KeystoreInterceptor : BaseKeystoreInterceptor() {
+ private val getTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "get")
+ private val generateKeyTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
+ private val getKeyCharacteristicsTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
+ private val exportKeyTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
+ private val attestKeyTransaction =
+ getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
+
+ override val serviceName = "android.security.keystore"
+ override val processName = "keystore"
+ override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
+
+ private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
+
+ private val keyArguments = HashMap()
+ private val keyPairs = HashMap()
+
+ data class Key(val uid: Int, val alias: String)
+
+ override fun onPreTransact(
+ target: IBinder,
+ code: Int,
+ flags: Int,
+ callingUid: Int,
+ callingPid: Int,
+ data: Parcel
+ ): Result {
+ if (CertificateHacker.canHack()) {
+ if (code == getTransaction) {
+ if (Config.needHack(callingUid)) {
+ return Continue
+ } else if (Config.needGenerate(callingUid)) {
+ return Skip
+ }
+ } else if (Config.needGenerate(callingUid)) {
+ when (code) {
+ generateKeyTransaction -> {
+ kotlin.runCatching {
+ data.enforceInterface(DESCRIPTOR)
+ val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
+ val alias = data.readString()!!.extractAlias()
+ Logger.i("generateKeyTransaction uid $callingUid alias $alias")
+ val check = data.readInt()
+ val kma = KeymasterArguments()
+ val kgp = CertificateHacker.KeyGenParameters()
+ if (check == 1) {
+ kma.readFromParcel(data)
+ kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
+ kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
+ kgp.setEcCurveName(kgp.keySize)
+ kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
+ kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
+ kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
+ if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
+ try {
+ val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
+ getArgumentByTag.isAccessible = true
+ val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
+
+ val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
+ getLongTagValue.isAccessible = true
+ kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
+ } catch (ex: Exception) {
+ Logger.e("Read rsaPublicExponent error", ex)
+ }
+ }
+ keyArguments[Key(callingUid, alias)] = kgp
+ }
+
+ val kc = KeyCharacteristics()
+ kc.swEnforced = KeymasterArguments()
+ kc.hwEnforced = kma
+
+ val ksr = createSuccessKeystoreResponse()
+ callback.onFinished(ksr, kc)
+
+ return createSuccessReply()
+ }.onFailure {
+ Logger.e("generateKeyTransaction error", it)
+ }
+ }
+
+ getKeyCharacteristicsTransaction -> {
+ kotlin.runCatching {
+ data.enforceInterface(DESCRIPTOR)
+ val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
+ val alias = data.readString()!!.extractAlias()
+ Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
+ val kc = KeyCharacteristics()
+ val kma = KeymasterArguments()
+ kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
+ kc.swEnforced = KeymasterArguments()
+ kc.hwEnforced = kma
+
+ val ksr = createSuccessKeystoreResponse()
+ callback.onFinished(ksr, kc)
+
+ return createSuccessReply()
+ }.onFailure {
+ Logger.e("getKeyCharacteristicsTransaction error", it)
+ }
+ }
+
+ exportKeyTransaction -> {
+ kotlin.runCatching {
+ data.enforceInterface(DESCRIPTOR)
+ val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
+ val alias = data.readString()!!.extractAlias()
+ Logger.i("exportKeyTransaction uid $callingUid alias $alias")
+ val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
+ keyPairs[Key(callingUid, alias)] = kp!!
+
+ val erP = Parcel.obtain()
+ erP.writeInt(KeyStore.NO_ERROR)
+ erP.writeByteArray(kp.public.encoded)
+ erP.setDataPosition(0)
+ val er = ExportResult.CREATOR.createFromParcel(erP)
+ erP.recycle()
+
+ callback.onFinished(er)
+
+ return createSuccessReply()
+ }.onFailure {
+ Logger.e("exportKeyTransaction error", it)
+ }
+ }
+
+ attestKeyTransaction -> {
+ kotlin.runCatching {
+ data.enforceInterface(DESCRIPTOR)
+ val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
+ val alias = data.readString()!!.extractAlias()
+ Logger.i("attestKeyTransaction uid $callingUid alias $alias")
+ val check = data.readInt()
+ val kma = KeymasterArguments()
+ if (check == 1) {
+ kma.readFromParcel(data)
+ val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
+
+ val ksr = createSuccessKeystoreResponse()
+
+ val key = Key(callingUid, alias)
+ val ka = keyArguments[key]!!
+ ka.attestationChallenge = attestationChallenge
+ val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
+
+ val kcc = KeymasterCertificateChain(chain)
+ callback.onFinished(ksr, kcc)
+ }
+
+ return createSuccessReply()
+ }.onFailure {
+ Logger.e("attestKeyTransaction error", it)
+ }
+ }
+ }
+ }
+ }
+ return Skip
+ }
+
+ override fun onPostTransact(
+ target: IBinder,
+ code: Int,
+ flags: Int,
+ callingUid: Int,
+ callingPid: Int,
+ data: Parcel,
+ reply: Parcel?,
+ resultCode: Int
+ ): Result {
+ if (target != keystore || code != getTransaction || reply == null) return Skip
+ if (reply.hasException()) return Skip
+ val p = Parcel.obtain()
+ Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
+ try {
+ data.enforceInterface(DESCRIPTOR)
+ val alias = data.readString() ?: ""
+ var response = reply.createByteArray()
+ when {
+ alias.startsWith(Credentials.USER_CERTIFICATE) -> {
+ response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid)
+ Logger.i("Hacked leaf certificate for uid=$callingUid")
+ return createByteArrayReply(response)
+ }
+ alias.startsWith(Credentials.CA_CERTIFICATE) -> {
+ response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid)
+ Logger.i("Hacked CA certificate chain for uid=$callingUid")
+ return createByteArrayReply(response)
+ }
+ else -> p.recycle()
+ }
+ } catch (t: Throwable) {
+ Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
+ p.recycle()
+ }
+ return Skip
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/SecurityLevelInterceptor.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/SecurityLevelInterceptor.kt
index 2a5494b..862fc52 100644
--- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/SecurityLevelInterceptor.kt
+++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/interceptors/SecurityLevelInterceptor.kt
@@ -1,180 +1,180 @@
-/*
- * Copyright 2025 Dakkshesh
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-package io.github.beakthoven.TrickyStoreOSS.interceptors
-
-import android.hardware.security.keymint.KeyParameter
-import android.hardware.security.keymint.KeyParameterValue
-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 androidx.annotation.Keep
-import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
-import io.github.beakthoven.TrickyStoreOSS.core.config.Config
-import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
-import io.github.beakthoven.TrickyStoreOSS.getTransactCode
-import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
-import java.security.KeyPair
-import java.security.cert.Certificate
-import java.util.concurrent.ConcurrentHashMap
-
-class SecurityLevelInterceptor(
- private val original: IKeystoreSecurityLevel,
- private val level: Int
-) : BinderInterceptor() {
- companion object {
- private val generateKeyTransaction =
- getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
- private val deleteKeyTransaction =
- getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
- private val createOperationTransaction =
- getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
-
- @Keep
- val keys = ConcurrentHashMap()
-
- @Keep
- val keyPairs = ConcurrentHashMap>>()
-
- @Keep
- val skipLeafHacks = ConcurrentHashMap()
-
- @Keep
- fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
- keys[Key(uid, alias)]?.response
-
- @Keep
- fun getKeyPairs(uid: Int, alias: String): Pair>? =
- keyPairs[Key(uid, alias)]
-
- @Keep
- fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
- skipLeafHacks[Key(uid, alias)] ?: false
- }
-
- 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) {
- 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 = CertificateHacker.KeyGenParameters(params)
- if (Config.needGenerate(callingUid)) {
- val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
- ?: return@runCatching
- keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
- val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: 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)
- } else if (Config.needHack(callingUid)) {
- if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
- Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
- val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
- ?: return@runCatching
- keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
- val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
- keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
- SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
- val p = Parcel.obtain()
- p.writeNoException()
- p.writeTypedObject(response.metadata, 0)
- return OverrideReply(0, p)
- } else {
- skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
- Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
- return Skip
- }
- }
- }.onFailure {
- Logger.e("parse key gen request", it)
- }
- }
- return Skip
- }
-
- private fun buildResponse(
- chain: List,
- params: CertificateHacker.KeyGenParameters,
- descriptor: KeyDescriptor
- ): KeyEntryResponse {
- val response = KeyEntryResponse()
- val metadata = KeyMetadata()
- metadata.keySecurityLevel = level
- metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
- val d = KeyDescriptor()
- d.domain = descriptor.domain
- d.nspace = descriptor.nspace
- metadata.key = d
- val authorizations = ArrayList()
- var a: Authorization
- for (i in params.purpose.toList()) {
- a = Authorization()
- a.keyParameter = KeyParameter()
- a.keyParameter.tag = Tag.PURPOSE
- a.keyParameter.value = KeyParameterValue.keyPurpose(i)
- a.securityLevel = level
- authorizations.add(a)
- }
- for (i in params.digest.toList()) {
- a = Authorization()
- a.keyParameter = KeyParameter()
- a.keyParameter.tag = Tag.DIGEST
- a.keyParameter.value = KeyParameterValue.digest(i)
- a.securityLevel = level
- authorizations.add(a)
- }
- a = Authorization()
- a.keyParameter = KeyParameter()
- a.keyParameter.tag = Tag.ALGORITHM
- a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
- a.securityLevel = level
- authorizations.add(a)
- a = Authorization()
- a.keyParameter = KeyParameter()
- a.keyParameter.tag = Tag.KEY_SIZE
- a.keyParameter.value = KeyParameterValue.integer(params.keySize)
- a.securityLevel = level
- authorizations.add(a)
- a = Authorization()
- a.keyParameter = KeyParameter()
- a.keyParameter.tag = Tag.EC_CURVE
- a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
- a.securityLevel = level
- authorizations.add(a)
- a = Authorization()
- a.keyParameter = KeyParameter()
- a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
- a.keyParameter.value = KeyParameterValue.boolValue(true)
- a.securityLevel = level
- authorizations.add(a)
- metadata.authorizations = authorizations.toTypedArray()
- response.metadata = metadata
- response.iSecurityLevel = original
- return response
- }
+/*
+ * Copyright 2025 Dakkshesh
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package io.github.beakthoven.TrickyStoreOSS.interceptors
+
+import android.hardware.security.keymint.KeyParameter
+import android.hardware.security.keymint.KeyParameterValue
+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 androidx.annotation.Keep
+import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
+import io.github.beakthoven.TrickyStoreOSS.core.config.Config
+import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
+import io.github.beakthoven.TrickyStoreOSS.getTransactCode
+import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
+import java.security.KeyPair
+import java.security.cert.Certificate
+import java.util.concurrent.ConcurrentHashMap
+
+class SecurityLevelInterceptor(
+ private val original: IKeystoreSecurityLevel,
+ private val level: Int
+) : BinderInterceptor() {
+ companion object {
+ private val generateKeyTransaction =
+ getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
+ private val deleteKeyTransaction =
+ getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
+ private val createOperationTransaction =
+ getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
+
+ @Keep
+ val keys = ConcurrentHashMap()
+
+ @Keep
+ val keyPairs = ConcurrentHashMap>>()
+
+ @Keep
+ val skipLeafHacks = ConcurrentHashMap()
+
+ @Keep
+ fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
+ keys[Key(uid, alias)]?.response
+
+ @Keep
+ fun getKeyPairs(uid: Int, alias: String): Pair>? =
+ keyPairs[Key(uid, alias)]
+
+ @Keep
+ fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
+ skipLeafHacks[Key(uid, alias)] ?: false
+ }
+
+ 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) {
+ 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 = CertificateHacker.KeyGenParameters(params)
+ if (Config.needGenerate(callingUid)) {
+ val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
+ ?: return@runCatching
+ keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
+ val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: 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)
+ } else if (Config.needHack(callingUid)) {
+ if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
+ Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
+ val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
+ ?: return@runCatching
+ keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
+ val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
+ keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
+ SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
+ val p = Parcel.obtain()
+ p.writeNoException()
+ p.writeTypedObject(response.metadata, 0)
+ return OverrideReply(0, p)
+ } else {
+ skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
+ Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
+ return Skip
+ }
+ }
+ }.onFailure {
+ Logger.e("parse key gen request", it)
+ }
+ }
+ return Skip
+ }
+
+ private fun buildResponse(
+ chain: List,
+ params: CertificateHacker.KeyGenParameters,
+ descriptor: KeyDescriptor
+ ): KeyEntryResponse {
+ val response = KeyEntryResponse()
+ val metadata = KeyMetadata()
+ metadata.keySecurityLevel = level
+ metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
+ val d = KeyDescriptor()
+ d.domain = descriptor.domain
+ d.nspace = descriptor.nspace
+ metadata.key = d
+ val authorizations = ArrayList()
+ var a: Authorization
+ for (i in params.purpose.toList()) {
+ a = Authorization()
+ a.keyParameter = KeyParameter()
+ a.keyParameter.tag = Tag.PURPOSE
+ a.keyParameter.value = KeyParameterValue.keyPurpose(i)
+ a.securityLevel = level
+ authorizations.add(a)
+ }
+ for (i in params.digest.toList()) {
+ a = Authorization()
+ a.keyParameter = KeyParameter()
+ a.keyParameter.tag = Tag.DIGEST
+ a.keyParameter.value = KeyParameterValue.digest(i)
+ a.securityLevel = level
+ authorizations.add(a)
+ }
+ a = Authorization()
+ a.keyParameter = KeyParameter()
+ a.keyParameter.tag = Tag.ALGORITHM
+ a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
+ a.securityLevel = level
+ authorizations.add(a)
+ a = Authorization()
+ a.keyParameter = KeyParameter()
+ a.keyParameter.tag = Tag.KEY_SIZE
+ a.keyParameter.value = KeyParameterValue.integer(params.keySize)
+ a.securityLevel = level
+ authorizations.add(a)
+ a = Authorization()
+ a.keyParameter = KeyParameter()
+ a.keyParameter.tag = Tag.EC_CURVE
+ a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
+ a.securityLevel = level
+ authorizations.add(a)
+ a = Authorization()
+ a.keyParameter = KeyParameter()
+ a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
+ a.keyParameter.value = KeyParameterValue.boolValue(true)
+ a.securityLevel = level
+ authorizations.add(a)
+ metadata.authorizations = authorizations.toTypedArray()
+ response.metadata = metadata
+ response.iSecurityLevel = original
+ return response
+ }
}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 11db7c9..de08903 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Aug 04 09:32:06 IST 2025
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
+#Mon Aug 04 09:32:06 IST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f..107acd3 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,89 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/module/keybox.xml b/module/keybox.xml
index 9c02996..fd21ae4 100644
--- a/module/keybox.xml
+++ b/module/keybox.xml
@@ -1,114 +1,114 @@
-
-
- 1
-
-
-
- -----BEGIN EC PRIVATE KEY-----
- MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
- AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
- uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
- -----END EC PRIVATE KEY-----
-
-
- 2
-
- -----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-----
-
-
- -----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-----
-
-
-
-
-
- -----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-----
-
-
- 2
-
- -----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-----
-
-
- -----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-----
-
-
-
-
-
+
+
+ 1
+
+
+
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
+ AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
+ uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
+ -----END EC PRIVATE KEY-----
+
+
+ 2
+
+ -----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-----
+
+
+ -----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-----
+
+
+
+
+
+ -----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-----
+
+
+ 2
+
+ -----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-----
+
+
+ -----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-----
+
+
+
+
+