src: dos2unix

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
This commit is contained in:
Dakkshesh
2025-08-06 21:01:18 +05:30
parent fa2fa1c7d5
commit c5b82ef67d
16 changed files with 2822 additions and 2822 deletions
+17 -17
View File
@@ -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
+6 -6
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
-->
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
-->
<manifest/>
@@ -1,188 +1,188 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<Pair<String, Long>> 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 <beakthoven@gmail.com>
* 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<Pair<String, Long>> 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() }
File diff suppressed because it is too large Load Diff
@@ -1,160 +1,160 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<out T> {
data class Success<T>(val data: T) : CertificateResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = 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<X509Certificate> {
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<X509Certificate> {
return this?.let { bytes ->
try {
val certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
} catch (e: CertificateException) {
Log.w(TAG, "Couldn't parse certificates in keystore", e)
emptyList()
}
} ?: emptyList()
}
fun Collection<Certificate>.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<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
map { it.encoded }
}.onFailure {
Log.w(TAG, "Failed to convert certificates to byte array list", it)
}.getOrNull()
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
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<Certificate>): Result<Unit> {
return runCatching {
metadata.putCertificateChain(chain)
}
}
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
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<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() }
fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching {
metadata.putCertificateChain(chain).getOrThrow()
}
}
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
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 <beakthoven@gmail.com>
* 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<out T> {
data class Success<T>(val data: T) : CertificateResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = 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<X509Certificate> {
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<X509Certificate> {
return this?.let { bytes ->
try {
val certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
} catch (e: CertificateException) {
Log.w(TAG, "Couldn't parse certificates in keystore", e)
emptyList()
}
} ?: emptyList()
}
fun Collection<Certificate>.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<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
map { it.encoded }
}.onFailure {
Log.w(TAG, "Failed to convert certificates to byte array list", it)
}.getOrNull()
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
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<Certificate>): Result<Unit> {
return runCatching {
metadata.putCertificateChain(chain)
}
}
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
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<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() }
fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching {
metadata.putCertificateChain(chain).getOrThrow()
}
}
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
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
}
}
}
@@ -1,155 +1,155 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<String, String>) : 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<String, String> {
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<String>,
index: Int,
tagCounts: MutableMap<String, Int>
): Map<String, String> {
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<String>,
index: Int,
tagCounts: MutableMap<String, Int>,
currentTag: String,
indexPart: String
): Map<String, String> {
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<String>,
index: Int
): Map<String, String> {
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<String, String> {
val attributes = mutableMapOf<String, String>()
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 <beakthoven@gmail.com>
* 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<String, String>) : 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<String, String> {
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<String>,
index: Int,
tagCounts: MutableMap<String, Int>
): Map<String, String> {
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<String>,
index: Int,
tagCounts: MutableMap<String, Int>,
currentTag: String,
indexPart: String
): Map<String, String> {
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<String>,
index: Int
): Map<String, String> {
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<String, String> {
val attributes = mutableMapOf<String, String>()
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)
@@ -1,267 +1,267 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<String>()
private val generatePackages = mutableSetOf<String>()
private val packageModes = mutableMapOf<String, Mode>()
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<String, String>()
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 <beakthoven@gmail.com>
* 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<String>()
private val generatePackages = mutableSetOf<String>()
private val packageModes = mutableMapOf<String, Mode>()
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<String, String>()
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
)
@@ -1,64 +1,64 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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 <beakthoven@gmail.com>
* 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())
}
}
}
@@ -1,181 +1,181 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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 <beakthoven@gmail.com>
* 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()
}
}
}
}
@@ -1,142 +1,142 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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 <T : Parcelable?> 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 <beakthoven@gmail.com>
* 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 <T : Parcelable?> 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
}
}
@@ -1,158 +1,158 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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 <beakthoven@gmail.com>
* 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
}
}
@@ -1,238 +1,238 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<Key, CertificateHacker.KeyGenParameters>()
private val keyPairs = HashMap<Key, KeyPair>()
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 <beakthoven@gmail.com>
* 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<Key, CertificateHacker.KeyGenParameters>()
private val keyPairs = HashMap<Key, KeyPair>()
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
}
}
@@ -1,180 +1,180 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<Key, Info>()
@Keep
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
@Keep
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
@Keep
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
keys[Key(uid, alias)]?.response
@Keep
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
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<Certificate>,
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<Authorization>()
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<Authorization>()
response.metadata = metadata
response.iSecurityLevel = original
return response
}
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* 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<Key, Info>()
@Keep
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
@Keep
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
@Keep
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
keys[Key(uid, alias)]?.response
@Keep
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
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<Certificate>,
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<Authorization>()
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<Authorization>()
response.metadata = metadata
response.iSecurityLevel = original
return response
}
}
+6 -6
View File
@@ -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
Vendored
+89 -89
View File
@@ -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
+114 -114
View File
@@ -1,114 +1,114 @@
<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="sw">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
-----END EC PRIVATE KEY-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem">
-----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-----
</Certificate>
<Certificate format="pem">
-----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-----
</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
-----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-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem">
-----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-----
</Certificate>
<Certificate format="pem">
-----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-----
</Certificate>
</CertificateChain>
</Key>
</Keybox>
</AndroidAttestation>
<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="sw">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
-----END EC PRIVATE KEY-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem">
-----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-----
</Certificate>
<Certificate format="pem">
-----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-----
</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
-----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-----
</PrivateKey>
<CertificateChain>
<NumberOfCertificates>2</NumberOfCertificates>
<Certificate format="pem">
-----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-----
</Certificate>
<Certificate format="pem">
-----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-----
</Certificate>
</CertificateChain>
</Key>
</Keybox>
</AndroidAttestation>