+17
-17
@@ -1,17 +1,17 @@
|
|||||||
BasedOnStyle: LLVM
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
Standard: c++20
|
Standard: c++20
|
||||||
|
|
||||||
ColumnLimit: 135
|
ColumnLimit: 135
|
||||||
|
|
||||||
AlignEscapedNewlines: Left
|
AlignEscapedNewlines: Left
|
||||||
AllowShortFunctionsOnASingleLine: Empty
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
AllowShortLambdasOnASingleLine: Empty
|
AllowShortLambdasOnASingleLine: Empty
|
||||||
AlwaysBreakTemplateDeclarations: true
|
AlwaysBreakTemplateDeclarations: true
|
||||||
IndentPPDirectives: AfterHash
|
IndentPPDirectives: AfterHash
|
||||||
|
|
||||||
AccessModifierOffset: -4
|
AccessModifierOffset: -4
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
UseTab: Never
|
UseTab: Never
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<manifest/>
|
<manifest/>
|
||||||
@@ -1,188 +1,188 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS
|
package io.github.beakthoven.TrickyStoreOSS
|
||||||
|
|
||||||
import android.content.pm.IPackageManager
|
import android.content.pm.IPackageManager
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ServiceManager
|
import android.os.ServiceManager
|
||||||
import android.os.SystemProperties
|
import android.os.SystemProperties
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
|
import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
import org.bouncycastle.asn1.ASN1Integer
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
import org.bouncycastle.asn1.DERSequence
|
import org.bouncycastle.asn1.DERSequence
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.concurrent.ThreadLocalRandom
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
|
||||||
fun getTransactCode(clazz: Class<*>, method: String): Int =
|
fun getTransactCode(clazz: Class<*>, method: String): Int =
|
||||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
|
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
|
||||||
.getInt(null)
|
.getInt(null)
|
||||||
|
|
||||||
val bootHash: ByteArray by lazy {
|
val bootHash: ByteArray by lazy {
|
||||||
getBootHashFromProp() ?: randomBytes()
|
getBootHashFromProp() ?: randomBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
val bootKey: ByteArray by lazy {
|
val bootKey: ByteArray by lazy {
|
||||||
randomBytes()
|
randomBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
private fun getBootHashFromProp(): ByteArray? {
|
private fun getBootHashFromProp(): ByteArray? {
|
||||||
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
|
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
|
||||||
return if (digest.length == 64) digest.hexToByteArray() else null
|
return if (digest.length == 64) digest.hexToByteArray() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun randomBytes(): ByteArray = ByteArray(32).also {
|
private fun randomBytes(): ByteArray = ByteArray(32).also {
|
||||||
ThreadLocalRandom.current().nextBytes(it)
|
ThreadLocalRandom.current().nextBytes(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val patchLevel: Int
|
val patchLevel: Int
|
||||||
get() = getCustomPatchLevel("system", false)
|
get() = getCustomPatchLevel("system", false)
|
||||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||||
|
|
||||||
val patchLevelLong: Int
|
val patchLevelLong: Int
|
||||||
get() = getCustomPatchLevel("system", true)
|
get() = getCustomPatchLevel("system", true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||||
|
|
||||||
val vendorPatchLevel: Int
|
val vendorPatchLevel: Int
|
||||||
get() = getCustomPatchLevel("vendor", false)
|
get() = getCustomPatchLevel("vendor", false)
|
||||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||||
|
|
||||||
val vendorPatchLevelLong: Int
|
val vendorPatchLevelLong: Int
|
||||||
get() = getCustomPatchLevel("vendor", true)
|
get() = getCustomPatchLevel("vendor", true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||||
|
|
||||||
val bootPatchLevel: Int
|
val bootPatchLevel: Int
|
||||||
get() = getCustomPatchLevel("boot", false)
|
get() = getCustomPatchLevel("boot", false)
|
||||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||||
|
|
||||||
val bootPatchLevelLong: Int
|
val bootPatchLevelLong: Int
|
||||||
get() = getCustomPatchLevel("boot", true)
|
get() = getCustomPatchLevel("boot", true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||||
|
|
||||||
private val customPatchLevel: CustomPatchLevel?
|
private val customPatchLevel: CustomPatchLevel?
|
||||||
get() = Config._customPatchLevel
|
get() = Config._customPatchLevel
|
||||||
|
|
||||||
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
|
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
|
||||||
val config = customPatchLevel ?: return null
|
val config = customPatchLevel ?: return null
|
||||||
val value = when (component) {
|
val value = when (component) {
|
||||||
"system" -> config.system ?: config.all
|
"system" -> config.system ?: config.all
|
||||||
"vendor" -> config.vendor ?: config.all
|
"vendor" -> config.vendor ?: config.all
|
||||||
"boot" -> config.boot ?: config.all
|
"boot" -> config.boot ?: config.all
|
||||||
else -> config.all
|
else -> config.all
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
when {
|
when {
|
||||||
value.equals("no", ignoreCase = true) -> return null
|
value.equals("no", ignoreCase = true) -> return null
|
||||||
value.equals("prop", ignoreCase = true) -> return null
|
value.equals("prop", ignoreCase = true) -> return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsePatchLevelValue(value, component, isLong)
|
return parsePatchLevelValue(value, component, isLong)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
|
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
|
||||||
val normalized = value.replace("-", "")
|
val normalized = value.replace("-", "")
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
when (normalized.length) {
|
when (normalized.length) {
|
||||||
8 -> {
|
8 -> {
|
||||||
val year = normalized.substring(0, 4).toInt()
|
val year = normalized.substring(0, 4).toInt()
|
||||||
val month = normalized.substring(4, 6).toInt()
|
val month = normalized.substring(4, 6).toInt()
|
||||||
val day = normalized.substring(6, 8).toInt()
|
val day = normalized.substring(6, 8).toInt()
|
||||||
if (isLong) year * 10000 + month * 100 + day
|
if (isLong) year * 10000 + month * 100 + day
|
||||||
else year * 100 + month
|
else year * 100 + month
|
||||||
}
|
}
|
||||||
6 -> {
|
6 -> {
|
||||||
val year = normalized.substring(0, 4).toInt()
|
val year = normalized.substring(0, 4).toInt()
|
||||||
val month = normalized.substring(4, 6).toInt()
|
val month = normalized.substring(4, 6).toInt()
|
||||||
if (isLong) year * 10000 + month * 100
|
if (isLong) year * 10000 + month * 100
|
||||||
else year * 100 + month
|
else year * 100 + month
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Logger.e("Invalid patch level length for $component: $normalized")
|
Logger.e("Invalid patch level length for $component: $normalized")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
Logger.e("Patch level parse error for $component=$value", e)
|
Logger.e("Patch level parse error for $component=$value", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val osVersion: Int
|
val osVersion: Int
|
||||||
get() = getOsVersion(Build.VERSION.SDK_INT)
|
get() = getOsVersion(Build.VERSION.SDK_INT)
|
||||||
|
|
||||||
private val osVersionMap = mapOf(
|
private val osVersionMap = mapOf(
|
||||||
Build.VERSION_CODES.BAKLAVA to 160000,
|
Build.VERSION_CODES.BAKLAVA to 160000,
|
||||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
|
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
|
||||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
|
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
|
||||||
Build.VERSION_CODES.TIRAMISU to 130000,
|
Build.VERSION_CODES.TIRAMISU to 130000,
|
||||||
Build.VERSION_CODES.S_V2 to 120100,
|
Build.VERSION_CODES.S_V2 to 120100,
|
||||||
Build.VERSION_CODES.S to 120000,
|
Build.VERSION_CODES.S to 120000,
|
||||||
Build.VERSION_CODES.R to 110000,
|
Build.VERSION_CODES.R to 110000,
|
||||||
Build.VERSION_CODES.Q to 100000
|
Build.VERSION_CODES.Q to 100000
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000
|
private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000
|
||||||
|
|
||||||
fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
|
fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
|
||||||
val parts = split("-")
|
val parts = split("-")
|
||||||
when {
|
when {
|
||||||
isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
|
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()
|
parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
|
||||||
else -> throw IllegalArgumentException("Invalid patch level format: $this")
|
else -> throw IllegalArgumentException("Invalid patch level format: $this")
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("Invalid patch level format: $this", it)
|
Logger.e("Invalid patch level format: $this", it)
|
||||||
}.getOrDefault(202404)
|
}.getOrDefault(202404)
|
||||||
|
|
||||||
fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) =
|
fun IPackageManager.getPackageInfoCompat(name: String, flags: Long, userId: Int) =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
getPackageInfo(name, flags, userId)
|
getPackageInfo(name, flags, userId)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
getPackageInfo(name, flags.toInt(), userId)
|
getPackageInfo(name, flags.toInt(), userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val apexInfos: List<Pair<String, Long>> by lazy {
|
val apexInfos: List<Pair<String, Long>> by lazy {
|
||||||
runCatching {
|
runCatching {
|
||||||
val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||||
val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
|
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
packages.list
|
packages.list
|
||||||
.map { it.packageName to it.longVersionCode }
|
.map { it.packageName to it.longVersionCode }
|
||||||
.sortedBy { it.first }
|
.sortedBy { it.first }
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
Logger.e("Failed to get APEX package information")
|
Logger.e("Failed to get APEX package information")
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val moduleHash: ByteArray by lazy {
|
val moduleHash: ByteArray by lazy {
|
||||||
runCatching {
|
runCatching {
|
||||||
val encodables = apexInfos.flatMap { (packageName, versionCode) ->
|
val encodables = apexInfos.flatMap { (packageName, versionCode) ->
|
||||||
listOf(
|
listOf(
|
||||||
DEROctetString(packageName.toByteArray()),
|
DEROctetString(packageName.toByteArray()),
|
||||||
ASN1Integer(versionCode)
|
ASN1Integer(versionCode)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sequence = DERSequence(encodables.toTypedArray())
|
val sequence = DERSequence(encodables.toTypedArray())
|
||||||
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
Logger.e("Failed to compute module hash", it)
|
Logger.e("Failed to compute module hash", it)
|
||||||
ByteArray(32)
|
ByteArray(32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
|
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>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS
|
package io.github.beakthoven.TrickyStoreOSS
|
||||||
|
|
||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
import android.system.keystore2.KeyMetadata
|
import android.system.keystore2.KeyMetadata
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
|
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.CertificateException
|
import java.security.cert.CertificateException
|
||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
object CertificateUtils {
|
object CertificateUtils {
|
||||||
private const val TAG = "CertificateUtils"
|
private const val TAG = "CertificateUtils"
|
||||||
|
|
||||||
sealed class CertificateResult<out T> {
|
sealed class CertificateResult<out T> {
|
||||||
data class Success<T>(val data: T) : CertificateResult<T>()
|
data class Success<T>(val data: T) : CertificateResult<T>()
|
||||||
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
|
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
|
||||||
|
|
||||||
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = when (this) {
|
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = when (this) {
|
||||||
is Success -> Success(transform(data))
|
is Success -> Success(transform(data))
|
||||||
is Error -> this
|
is Error -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrNull(): T? = when (this) {
|
fun getOrNull(): T? = when (this) {
|
||||||
is Success -> data
|
is Success -> data
|
||||||
is Error -> null
|
is Error -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray?.toCertificate(): X509Certificate? {
|
fun ByteArray?.toCertificate(): X509Certificate? {
|
||||||
return this?.let { bytes ->
|
return this?.let { bytes ->
|
||||||
try {
|
try {
|
||||||
val certFactory = CertificateFactory.getInstance("X.509")
|
val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
|
certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
|
||||||
} catch (e: CertificateException) {
|
} catch (e: CertificateException) {
|
||||||
Log.w(TAG, "Couldn't parse certificate in keystore", e)
|
Log.w(TAG, "Couldn't parse certificate in keystore", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> {
|
fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> {
|
||||||
return try {
|
return try {
|
||||||
val certFactory = CertificateFactory.getInstance("X.509")
|
val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
|
val certificate = certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
|
||||||
CertificateResult.Success(certificate)
|
CertificateResult.Success(certificate)
|
||||||
} catch (e: CertificateException) {
|
} catch (e: CertificateException) {
|
||||||
CertificateResult.Error("Failed to parse certificate", e)
|
CertificateResult.Error("Failed to parse certificate", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun ByteArray?.toCertificates(): Collection<X509Certificate> {
|
fun ByteArray?.toCertificates(): Collection<X509Certificate> {
|
||||||
return this?.let { bytes ->
|
return this?.let { bytes ->
|
||||||
try {
|
try {
|
||||||
val certFactory = CertificateFactory.getInstance("X.509")
|
val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
|
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
|
||||||
} catch (e: CertificateException) {
|
} catch (e: CertificateException) {
|
||||||
Log.w(TAG, "Couldn't parse certificates in keystore", e)
|
Log.w(TAG, "Couldn't parse certificates in keystore", e)
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Collection<Certificate>.toByteArray(): ByteArray? = runCatching {
|
fun Collection<Certificate>.toByteArray(): ByteArray? = runCatching {
|
||||||
ByteArrayOutputStream().use { outputStream ->
|
ByteArrayOutputStream().use { outputStream ->
|
||||||
forEach { cert -> outputStream.write(cert.encoded) }
|
forEach { cert -> outputStream.write(cert.encoded) }
|
||||||
outputStream.toByteArray()
|
outputStream.toByteArray()
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Log.w(TAG, "Failed to convert certificates to byte array", it)
|
Log.w(TAG, "Failed to convert certificates to byte array", it)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
|
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
|
||||||
map { it.encoded }
|
map { it.encoded }
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Log.w(TAG, "Failed to convert certificates to byte array list", it)
|
Log.w(TAG, "Failed to convert certificates to byte array list", it)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
|
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
|
||||||
val metadata = this?.metadata ?: return null
|
val metadata = this?.metadata ?: return null
|
||||||
val leafCert = metadata.certificate?.toCertificate() ?: return null
|
val leafCert = metadata.certificate?.toCertificate() ?: return null
|
||||||
|
|
||||||
return when (val chainBytes = metadata.certificateChain) {
|
return when (val chainBytes = metadata.certificateChain) {
|
||||||
null -> arrayOf(leafCert)
|
null -> arrayOf(leafCert)
|
||||||
else -> {
|
else -> {
|
||||||
val additionalCerts = chainBytes.toCertificates()
|
val additionalCerts = chainBytes.toCertificates()
|
||||||
buildList {
|
buildList {
|
||||||
add(leafCert)
|
add(leafCert)
|
||||||
addAll(additionalCerts)
|
addAll(additionalCerts)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
metadata.putCertificateChain(chain)
|
metadata.putCertificateChain(chain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
if (chain.isEmpty()) return@runCatching
|
if (chain.isEmpty()) return@runCatching
|
||||||
|
|
||||||
certificate = chain[0].encoded
|
certificate = chain[0].encoded
|
||||||
|
|
||||||
if (chain.size > 1) {
|
if (chain.size > 1) {
|
||||||
ByteArrayOutputStream().use { output ->
|
ByteArrayOutputStream().use { output ->
|
||||||
for (i in 1 until chain.size) {
|
for (i in 1 until chain.size) {
|
||||||
output.write(chain[i].encoded)
|
output.write(chain[i].encoded)
|
||||||
}
|
}
|
||||||
certificateChain = output.toByteArray()
|
certificateChain = output.toByteArray()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
certificateChain = null
|
certificateChain = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() }
|
fun ByteArray?.toX509Certificate(): X509Certificate? = CertificateUtils.run { this@toX509Certificate.toCertificate() }
|
||||||
|
|
||||||
fun ByteArray?.toX509Certificates(): Collection<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() }
|
fun ByteArray?.toX509Certificates(): Collection<X509Certificate> = CertificateUtils.run { this@toX509Certificates.toCertificates() }
|
||||||
|
|
||||||
fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
|
fun Collection<Certificate>.encodedBytes(): ByteArray? = CertificateUtils.run { this@encodedBytes.toByteArray() }
|
||||||
|
|
||||||
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
|
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? = CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
|
||||||
|
|
||||||
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
metadata.putCertificateChain(chain).getOrThrow()
|
metadata.putCertificateChain(chain).getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
if (chain.isEmpty()) return@runCatching
|
if (chain.isEmpty()) return@runCatching
|
||||||
|
|
||||||
certificate = chain[0].encoded
|
certificate = chain[0].encoded
|
||||||
|
|
||||||
if (chain.size > 1) {
|
if (chain.size > 1) {
|
||||||
ByteArrayOutputStream().use { output ->
|
ByteArrayOutputStream().use { output ->
|
||||||
for (i in 1 until chain.size) {
|
for (i in 1 until chain.size) {
|
||||||
output.write(chain[i].encoded)
|
output.write(chain[i].encoded)
|
||||||
}
|
}
|
||||||
certificateChain = output.toByteArray()
|
certificateChain = output.toByteArray()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
certificateChain = null
|
certificateChain = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,155 +1,155 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS
|
package io.github.beakthoven.TrickyStoreOSS
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
import org.xmlpull.v1.XmlPullParserFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.StringReader
|
import java.io.StringReader
|
||||||
|
|
||||||
class XmlParser(private val xmlContent: String) {
|
class XmlParser(private val xmlContent: String) {
|
||||||
|
|
||||||
sealed class ParseResult {
|
sealed class ParseResult {
|
||||||
data class Success(val attributes: Map<String, String>) : ParseResult()
|
data class Success(val attributes: Map<String, String>) : ParseResult()
|
||||||
data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
|
data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun obtainPath(path: String): ParseResult {
|
fun obtainPath(path: String): ParseResult {
|
||||||
return try {
|
return try {
|
||||||
val factory = XmlPullParserFactory.newInstance()
|
val factory = XmlPullParserFactory.newInstance()
|
||||||
val parser = factory.newPullParser()
|
val parser = factory.newPullParser()
|
||||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||||
parser.setInput(StringReader(xmlContent))
|
parser.setInput(StringReader(xmlContent))
|
||||||
|
|
||||||
val tags = path.split(".").toTypedArray()
|
val tags = path.split(".").toTypedArray()
|
||||||
val result = readData(parser, tags, 0, mutableMapOf())
|
val result = readData(parser, tags, 0, mutableMapOf())
|
||||||
ParseResult.Success(result)
|
ParseResult.Success(result)
|
||||||
} catch (e: XmlPullParserException) {
|
} catch (e: XmlPullParserException) {
|
||||||
ParseResult.Error("XML parsing error: ${e.message}", e)
|
ParseResult.Error("XML parsing error: ${e.message}", e)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
ParseResult.Error("IO error while parsing XML: ${e.message}", e)
|
ParseResult.Error("IO error while parsing XML: ${e.message}", e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ParseResult.Error("Unexpected error: ${e.message}", e)
|
ParseResult.Error("Unexpected error: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun obtainPathLegacy(path: String): Map<String, String> {
|
fun obtainPathLegacy(path: String): Map<String, String> {
|
||||||
when (val result = obtainPath(path)) {
|
when (val result = obtainPath(path)) {
|
||||||
is ParseResult.Success -> return result.attributes
|
is ParseResult.Success -> return result.attributes
|
||||||
is ParseResult.Error -> throw result.cause ?: Exception(result.message)
|
is ParseResult.Error -> throw result.cause ?: Exception(result.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun readData(
|
private fun readData(
|
||||||
parser: XmlPullParser,
|
parser: XmlPullParser,
|
||||||
tags: Array<String>,
|
tags: Array<String>,
|
||||||
index: Int,
|
index: Int,
|
||||||
tagCounts: MutableMap<String, Int>
|
tagCounts: MutableMap<String, Int>
|
||||||
): Map<String, String> {
|
): Map<String, String> {
|
||||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentTag = parser.name ?: continue
|
val currentTag = parser.name ?: continue
|
||||||
val targetTag = tags[index]
|
val targetTag = tags[index]
|
||||||
val tagParts = targetTag.split("[")
|
val tagParts = targetTag.split("[")
|
||||||
val baseTagName = tagParts[0]
|
val baseTagName = tagParts[0]
|
||||||
|
|
||||||
if (currentTag == baseTagName) {
|
if (currentTag == baseTagName) {
|
||||||
return if (tagParts.size > 1) {
|
return if (tagParts.size > 1) {
|
||||||
handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
|
handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
|
||||||
} else {
|
} else {
|
||||||
handleRegularTag(parser, tags, index)
|
handleRegularTag(parser, tags, index)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
skipCurrentElement(parser)
|
skipCurrentElement(parser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
|
throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun handleIndexedTag(
|
private fun handleIndexedTag(
|
||||||
parser: XmlPullParser,
|
parser: XmlPullParser,
|
||||||
tags: Array<String>,
|
tags: Array<String>,
|
||||||
index: Int,
|
index: Int,
|
||||||
tagCounts: MutableMap<String, Int>,
|
tagCounts: MutableMap<String, Int>,
|
||||||
currentTag: String,
|
currentTag: String,
|
||||||
indexPart: String
|
indexPart: String
|
||||||
): Map<String, String> {
|
): Map<String, String> {
|
||||||
val targetIndex = indexPart.replace("]", "").toIntOrNull()
|
val targetIndex = indexPart.replace("]", "").toIntOrNull()
|
||||||
?: throw XmlPullParserException("Invalid index in tag: $indexPart")
|
?: throw XmlPullParserException("Invalid index in tag: $indexPart")
|
||||||
|
|
||||||
val currentCount = tagCounts.getOrDefault(currentTag, 0)
|
val currentCount = tagCounts.getOrDefault(currentTag, 0)
|
||||||
|
|
||||||
return if (currentCount < targetIndex) {
|
return if (currentCount < targetIndex) {
|
||||||
tagCounts[currentTag] = currentCount + 1
|
tagCounts[currentTag] = currentCount + 1
|
||||||
readData(parser, tags, index, tagCounts)
|
readData(parser, tags, index, tagCounts)
|
||||||
} else {
|
} else {
|
||||||
if (index == tags.size - 1) {
|
if (index == tags.size - 1) {
|
||||||
readAttributes(parser)
|
readAttributes(parser)
|
||||||
} else {
|
} else {
|
||||||
readData(parser, tags, index + 1, tagCounts)
|
readData(parser, tags, index + 1, tagCounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun handleRegularTag(
|
private fun handleRegularTag(
|
||||||
parser: XmlPullParser,
|
parser: XmlPullParser,
|
||||||
tags: Array<String>,
|
tags: Array<String>,
|
||||||
index: Int
|
index: Int
|
||||||
): Map<String, String> {
|
): Map<String, String> {
|
||||||
return if (index == tags.size - 1) {
|
return if (index == tags.size - 1) {
|
||||||
readAttributes(parser)
|
readAttributes(parser)
|
||||||
} else {
|
} else {
|
||||||
readData(parser, tags, index + 1, mutableMapOf())
|
readData(parser, tags, index + 1, mutableMapOf())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun readAttributes(parser: XmlPullParser): Map<String, String> {
|
private fun readAttributes(parser: XmlPullParser): Map<String, String> {
|
||||||
val attributes = mutableMapOf<String, String>()
|
val attributes = mutableMapOf<String, String>()
|
||||||
|
|
||||||
for (i in 0 until parser.attributeCount) {
|
for (i in 0 until parser.attributeCount) {
|
||||||
val name = parser.getAttributeName(i)
|
val name = parser.getAttributeName(i)
|
||||||
val value = parser.getAttributeValue(i)
|
val value = parser.getAttributeValue(i)
|
||||||
if (name != null && value != null) {
|
if (name != null && value != null) {
|
||||||
attributes[name] = value
|
attributes[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parser.next() == XmlPullParser.TEXT) {
|
if (parser.next() == XmlPullParser.TEXT) {
|
||||||
parser.text?.let { text ->
|
parser.text?.let { text ->
|
||||||
attributes["text"] = text
|
attributes["text"] = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(XmlPullParserException::class, IOException::class)
|
@Throws(XmlPullParserException::class, IOException::class)
|
||||||
private fun skipCurrentElement(parser: XmlPullParser) {
|
private fun skipCurrentElement(parser: XmlPullParser) {
|
||||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||||
throw IllegalStateException("Parser must be positioned at START_TAG")
|
throw IllegalStateException("Parser must be positioned at START_TAG")
|
||||||
}
|
}
|
||||||
|
|
||||||
var depth = 1
|
var depth = 1
|
||||||
while (depth != 0) {
|
while (depth != 0) {
|
||||||
when (parser.next()) {
|
when (parser.next()) {
|
||||||
XmlPullParser.END_TAG -> depth--
|
XmlPullParser.END_TAG -> depth--
|
||||||
XmlPullParser.START_TAG -> depth++
|
XmlPullParser.START_TAG -> depth++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.toXmlParser(): XmlParser = XmlParser(this)
|
fun String.toXmlParser(): XmlParser = XmlParser(this)
|
||||||
@@ -1,267 +1,267 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.core.config
|
package io.github.beakthoven.TrickyStoreOSS.core.config
|
||||||
|
|
||||||
import android.content.pm.IPackageManager
|
import android.content.pm.IPackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.FileObserver
|
import android.os.FileObserver
|
||||||
import android.os.ServiceManager
|
import android.os.ServiceManager
|
||||||
import android.security.keystore.KeyGenParameterSpec
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
|
|
||||||
object Config {
|
object Config {
|
||||||
private val hackPackages = mutableSetOf<String>()
|
private val hackPackages = mutableSetOf<String>()
|
||||||
private val generatePackages = mutableSetOf<String>()
|
private val generatePackages = mutableSetOf<String>()
|
||||||
private val packageModes = mutableMapOf<String, Mode>()
|
private val packageModes = mutableMapOf<String, Mode>()
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
AUTO, LEAF_HACK, GENERATE
|
AUTO, LEAF_HACK, GENERATE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTargetPackages(f: File?) = runCatching {
|
private fun updateTargetPackages(f: File?) = runCatching {
|
||||||
hackPackages.clear()
|
hackPackages.clear()
|
||||||
generatePackages.clear()
|
generatePackages.clear()
|
||||||
packageModes.clear()
|
packageModes.clear()
|
||||||
// Default: always generate for these
|
// Default: always generate for these
|
||||||
listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach {
|
listOf("com.google.android.gsf", "com.google.android.gms", "com.android.vending").forEach {
|
||||||
generatePackages.add(it)
|
generatePackages.add(it)
|
||||||
packageModes[it] = Mode.GENERATE
|
packageModes[it] = Mode.GENERATE
|
||||||
}
|
}
|
||||||
f?.readLines()?.forEach {
|
f?.readLines()?.forEach {
|
||||||
if (it.isNotBlank() && !it.startsWith("#")) {
|
if (it.isNotBlank() && !it.startsWith("#")) {
|
||||||
val n = it.trim()
|
val n = it.trim()
|
||||||
when {
|
when {
|
||||||
n.endsWith("!") -> {
|
n.endsWith("!") -> {
|
||||||
val pkg = n.removeSuffix("!").trim()
|
val pkg = n.removeSuffix("!").trim()
|
||||||
generatePackages.add(pkg)
|
generatePackages.add(pkg)
|
||||||
packageModes[pkg] = Mode.GENERATE
|
packageModes[pkg] = Mode.GENERATE
|
||||||
}
|
}
|
||||||
n.endsWith("?") -> {
|
n.endsWith("?") -> {
|
||||||
val pkg = n.removeSuffix("?").trim()
|
val pkg = n.removeSuffix("?").trim()
|
||||||
hackPackages.add(pkg)
|
hackPackages.add(pkg)
|
||||||
packageModes[pkg] = Mode.LEAF_HACK
|
packageModes[pkg] = Mode.LEAF_HACK
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Auto mode
|
// Auto mode
|
||||||
packageModes[n] = Mode.AUTO
|
packageModes[n] = Mode.AUTO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes")
|
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes")
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("failed to update target files", it)
|
Logger.e("failed to update target files", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateKeyBox(f: File?) = runCatching {
|
private fun updateKeyBox(f: File?) = runCatching {
|
||||||
CertificateHacker.readFromXml(f?.readText())
|
CertificateHacker.readFromXml(f?.readText())
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("failed to update keybox", it)
|
Logger.e("failed to update keybox", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val CONFIG_PATH = "/data/adb/tricky_store"
|
private const val CONFIG_PATH = "/data/adb/tricky_store"
|
||||||
private const val TARGET_FILE = "target.txt"
|
private const val TARGET_FILE = "target.txt"
|
||||||
private const val KEYBOX_FILE = "keybox.xml"
|
private const val KEYBOX_FILE = "keybox.xml"
|
||||||
private const val TEE_STATUS_FILE = "tee_status"
|
private const val TEE_STATUS_FILE = "tee_status"
|
||||||
private const val PATCHLEVEL_FILE = "security_patch.txt"
|
private const val PATCHLEVEL_FILE = "security_patch.txt"
|
||||||
private val root = File(CONFIG_PATH)
|
private val root = File(CONFIG_PATH)
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var teeBroken: Boolean? = null
|
private var teeBroken: Boolean? = null
|
||||||
|
|
||||||
private fun isTEEWorking(): Boolean {
|
private fun isTEEWorking(): Boolean {
|
||||||
val alias = "tee_attest_test_key"
|
val alias = "tee_attest_test_key"
|
||||||
return try {
|
return try {
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
android.app.ActivityThread.initializeMainlineModules();
|
android.app.ActivityThread.initializeMainlineModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
android.security.keystore2.AndroidKeyStoreProvider.install();
|
android.security.keystore2.AndroidKeyStoreProvider.install();
|
||||||
} else {
|
} else {
|
||||||
android.security.keystore.AndroidKeyStoreProvider.install();
|
android.security.keystore.AndroidKeyStoreProvider.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||||
keyStore.load(null)
|
keyStore.load(null)
|
||||||
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance(
|
val keyPairGenerator = KeyPairGenerator.getInstance(
|
||||||
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
||||||
|
|
||||||
val challenge = ByteArray(16).apply {
|
val challenge = ByteArray(16).apply {
|
||||||
SecureRandom().nextBytes(this)
|
SecureRandom().nextBytes(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parameterSpec = KeyGenParameterSpec.Builder(
|
val parameterSpec = KeyGenParameterSpec.Builder(
|
||||||
alias,
|
alias,
|
||||||
KeyProperties.PURPOSE_SIGN
|
KeyProperties.PURPOSE_SIGN
|
||||||
)
|
)
|
||||||
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
||||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||||
.setAttestationChallenge(challenge)
|
.setAttestationChallenge(challenge)
|
||||||
.setIsStrongBoxBacked(false)
|
.setIsStrongBoxBacked(false)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
keyPairGenerator.initialize(parameterSpec)
|
keyPairGenerator.initialize(parameterSpec)
|
||||||
keyPairGenerator.generateKeyPair()
|
keyPairGenerator.generateKeyPair()
|
||||||
|
|
||||||
keyStore.deleteEntry(alias)
|
keyStore.deleteEntry(alias)
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e("TEE check failure: ${e.message}")
|
Logger.e("TEE check failure: ${e.message}")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun storeTEEStatus(root: File) {
|
private fun storeTEEStatus(root: File) {
|
||||||
val statusFile = File(root, TEE_STATUS_FILE)
|
val statusFile = File(root, TEE_STATUS_FILE)
|
||||||
val status = isTEEWorking()
|
val status = isTEEWorking()
|
||||||
teeBroken = !status
|
teeBroken = !status
|
||||||
try {
|
try {
|
||||||
statusFile.writeText("teeBroken=${!status}")
|
statusFile.writeText("teeBroken=${!status}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e("Failed to write TEE status: ${e.message}")
|
Logger.e("Failed to write TEE status: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadTEEStatus(root: File) {
|
private fun loadTEEStatus(root: File) {
|
||||||
val statusFile = File(root, TEE_STATUS_FILE)
|
val statusFile = File(root, TEE_STATUS_FILE)
|
||||||
if (statusFile.exists()) {
|
if (statusFile.exists()) {
|
||||||
val line = statusFile.readText().trim()
|
val line = statusFile.readText().trim()
|
||||||
teeBroken = line == "teeBroken=true"
|
teeBroken = line == "teeBroken=true"
|
||||||
} else {
|
} else {
|
||||||
teeBroken = null
|
teeBroken = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
|
object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
|
||||||
override fun onEvent(event: Int, path: String?) {
|
override fun onEvent(event: Int, path: String?) {
|
||||||
path ?: return
|
path ?: return
|
||||||
val f = when (event) {
|
val f = when (event) {
|
||||||
CLOSE_WRITE, MOVED_TO -> File(root, path)
|
CLOSE_WRITE, MOVED_TO -> File(root, path)
|
||||||
DELETE, MOVED_FROM -> null
|
DELETE, MOVED_FROM -> null
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
when (path) {
|
when (path) {
|
||||||
TARGET_FILE -> updateTargetPackages(f)
|
TARGET_FILE -> updateTargetPackages(f)
|
||||||
KEYBOX_FILE -> updateKeyBox(f)
|
KEYBOX_FILE -> updateKeyBox(f)
|
||||||
PATCHLEVEL_FILE -> updatePatchLevel(f)
|
PATCHLEVEL_FILE -> updatePatchLevel(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
root.mkdirs()
|
root.mkdirs()
|
||||||
val scope = File(root, TARGET_FILE)
|
val scope = File(root, TARGET_FILE)
|
||||||
if (scope.exists()) {
|
if (scope.exists()) {
|
||||||
updateTargetPackages(scope)
|
updateTargetPackages(scope)
|
||||||
} else {
|
} else {
|
||||||
Logger.e("target.txt file not found, please put it to $scope !")
|
Logger.e("target.txt file not found, please put it to $scope !")
|
||||||
}
|
}
|
||||||
val keybox = File(root, KEYBOX_FILE)
|
val keybox = File(root, KEYBOX_FILE)
|
||||||
if (!keybox.exists()) {
|
if (!keybox.exists()) {
|
||||||
Logger.e("keybox file not found, please put it to $keybox !")
|
Logger.e("keybox file not found, please put it to $keybox !")
|
||||||
} else {
|
} else {
|
||||||
updateKeyBox(keybox)
|
updateKeyBox(keybox)
|
||||||
}
|
}
|
||||||
storeTEEStatus(root)
|
storeTEEStatus(root)
|
||||||
val patchFile = File(root, PATCHLEVEL_FILE)
|
val patchFile = File(root, PATCHLEVEL_FILE)
|
||||||
updatePatchLevel(if (patchFile.exists()) patchFile else null)
|
updatePatchLevel(if (patchFile.exists()) patchFile else null)
|
||||||
ConfigObserver.startWatching()
|
ConfigObserver.startWatching()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var iPm: IPackageManager? = null
|
private var iPm: IPackageManager? = null
|
||||||
|
|
||||||
fun getPm(): IPackageManager? {
|
fun getPm(): IPackageManager? {
|
||||||
if (iPm == null) {
|
if (iPm == null) {
|
||||||
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
iPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||||
}
|
}
|
||||||
return iPm
|
return iPm
|
||||||
}
|
}
|
||||||
|
|
||||||
fun needHack(callingUid: Int): Boolean = kotlin.runCatching {
|
fun needHack(callingUid: Int): Boolean = kotlin.runCatching {
|
||||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||||
if (teeBroken == null) loadTEEStatus(root)
|
if (teeBroken == null) loadTEEStatus(root)
|
||||||
for (pkg in ps) {
|
for (pkg in ps) {
|
||||||
when (packageModes[pkg]) {
|
when (packageModes[pkg]) {
|
||||||
Mode.LEAF_HACK -> return true
|
Mode.LEAF_HACK -> return true
|
||||||
Mode.AUTO -> {
|
Mode.AUTO -> {
|
||||||
if (teeBroken == false) return true
|
if (teeBroken == false) return true
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
||||||
|
|
||||||
fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching {
|
fun needGenerate(callingUid: Int): Boolean = kotlin.runCatching {
|
||||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||||
if (teeBroken == null) loadTEEStatus(root)
|
if (teeBroken == null) loadTEEStatus(root)
|
||||||
for (pkg in ps) {
|
for (pkg in ps) {
|
||||||
when (packageModes[pkg]) {
|
when (packageModes[pkg]) {
|
||||||
Mode.GENERATE -> return true
|
Mode.GENERATE -> return true
|
||||||
Mode.AUTO -> {
|
Mode.AUTO -> {
|
||||||
if (teeBroken == true) return true
|
if (teeBroken == true) return true
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
}.onFailure { Logger.e("failed to get packages", it) }.getOrNull() ?: false
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var _customPatchLevel: CustomPatchLevel? = null
|
var _customPatchLevel: CustomPatchLevel? = null
|
||||||
|
|
||||||
fun updatePatchLevel(f: File?) = runCatching {
|
fun updatePatchLevel(f: File?) = runCatching {
|
||||||
if (f == null || !f.exists()) {
|
if (f == null || !f.exists()) {
|
||||||
_customPatchLevel = null
|
_customPatchLevel = null
|
||||||
return@runCatching
|
return@runCatching
|
||||||
}
|
}
|
||||||
val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
|
val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||||
if (lines.isEmpty()) {
|
if (lines.isEmpty()) {
|
||||||
_customPatchLevel = null
|
_customPatchLevel = null
|
||||||
return@runCatching
|
return@runCatching
|
||||||
}
|
}
|
||||||
if (lines.size == 1 && !lines[0].contains("=")) {
|
if (lines.size == 1 && !lines[0].contains("=")) {
|
||||||
_customPatchLevel = CustomPatchLevel(all = lines[0])
|
_customPatchLevel = CustomPatchLevel(all = lines[0])
|
||||||
return@runCatching
|
return@runCatching
|
||||||
}
|
}
|
||||||
val map = mutableMapOf<String, String>()
|
val map = mutableMapOf<String, String>()
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
val idx = line.indexOf('=')
|
val idx = line.indexOf('=')
|
||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
val key = line.substring(0, idx).trim().lowercase()
|
val key = line.substring(0, idx).trim().lowercase()
|
||||||
val value = line.substring(idx + 1).trim()
|
val value = line.substring(idx + 1).trim()
|
||||||
map[key] = value
|
map[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val all = map["all"]
|
val all = map["all"]
|
||||||
_customPatchLevel = CustomPatchLevel(
|
_customPatchLevel = CustomPatchLevel(
|
||||||
system = map["system"] ?: all,
|
system = map["system"] ?: all,
|
||||||
vendor = map["vendor"] ?: all,
|
vendor = map["vendor"] ?: all,
|
||||||
boot = map["boot"] ?: all,
|
boot = map["boot"] ?: all,
|
||||||
all = all
|
all = all
|
||||||
)
|
)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("failed to update patch level", it)
|
Logger.e("failed to update patch level", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CustomPatchLevel(
|
data class CustomPatchLevel(
|
||||||
val system: String? = null,
|
val system: String? = null,
|
||||||
val vendor: String? = null,
|
val vendor: String? = null,
|
||||||
val boot: String? = null,
|
val boot: String? = null,
|
||||||
val all: String? = null
|
val all: String? = null
|
||||||
)
|
)
|
||||||
@@ -1,64 +1,64 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.core.logging
|
package io.github.beakthoven.TrickyStoreOSS.core.logging
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
object Logger {
|
object Logger {
|
||||||
const val TAG = "TrickyStore"
|
const val TAG = "TrickyStore"
|
||||||
|
|
||||||
sealed class LogLevel(val priority: Int) {
|
sealed class LogLevel(val priority: Int) {
|
||||||
object Debug : LogLevel(Log.DEBUG)
|
object Debug : LogLevel(Log.DEBUG)
|
||||||
object Info : LogLevel(Log.INFO)
|
object Info : LogLevel(Log.INFO)
|
||||||
object Warning : LogLevel(Log.WARN)
|
object Warning : LogLevel(Log.WARN)
|
||||||
object Error : LogLevel(Log.ERROR)
|
object Error : LogLevel(Log.ERROR)
|
||||||
object Verbose : LogLevel(Log.VERBOSE)
|
object Verbose : LogLevel(Log.VERBOSE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun d(message: String) {
|
fun d(message: String) {
|
||||||
Log.d(TAG, message)
|
Log.d(TAG, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun e(message: String) {
|
fun e(message: String) {
|
||||||
Log.e(TAG, message)
|
Log.e(TAG, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun e(message: String, throwable: Throwable) {
|
fun e(message: String, throwable: Throwable) {
|
||||||
Log.e(TAG, "wtf: $message", throwable)
|
Log.e(TAG, "wtf: $message", throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun i(message: String) {
|
fun i(message: String) {
|
||||||
Log.i(TAG, message)
|
Log.i(TAG, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun w(message: String) {
|
fun w(message: String) {
|
||||||
Log.w(TAG, message)
|
Log.w(TAG, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun w(message: String, throwable: Throwable) {
|
fun w(message: String, throwable: Throwable) {
|
||||||
Log.w(TAG, message, throwable)
|
Log.w(TAG, message, throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun v(message: String) {
|
fun v(message: String) {
|
||||||
Log.v(TAG, message)
|
Log.v(TAG, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
|
fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
|
||||||
when (level) {
|
when (level) {
|
||||||
is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
|
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.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.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.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)
|
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) {
|
fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
|
||||||
if (condition && Log.isLoggable(TAG, level.priority)) {
|
if (condition && Log.isLoggable(TAG, level.priority)) {
|
||||||
log(level, messageProvider())
|
log(level, messageProvider())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+180
-180
@@ -1,181 +1,181 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||||
|
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
|
|
||||||
open class BinderInterceptor : Binder() {
|
open class BinderInterceptor : Binder() {
|
||||||
|
|
||||||
sealed class Result
|
sealed class Result
|
||||||
|
|
||||||
data object Skip : Result()
|
data object Skip : Result()
|
||||||
|
|
||||||
data object Continue : Result()
|
data object Continue : Result()
|
||||||
|
|
||||||
data class OverrideData(val data: Parcel) : Result()
|
data class OverrideData(val data: Parcel) : Result()
|
||||||
|
|
||||||
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
|
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
||||||
|
|
||||||
private const val REGISTER_INTERCEPTOR_CODE = 1
|
private const val REGISTER_INTERCEPTOR_CODE = 1
|
||||||
|
|
||||||
private const val PRE_TRANSACT_CODE = 1
|
private const val PRE_TRANSACT_CODE = 1
|
||||||
private const val POST_TRANSACT_CODE = 2
|
private const val POST_TRANSACT_CODE = 2
|
||||||
|
|
||||||
private const val RESULT_SKIP = 1
|
private const val RESULT_SKIP = 1
|
||||||
private const val RESULT_CONTINUE = 2
|
private const val RESULT_CONTINUE = 2
|
||||||
private const val RESULT_OVERRIDE_REPLY = 3
|
private const val RESULT_OVERRIDE_REPLY = 3
|
||||||
private const val RESULT_OVERRIDE_DATA = 4
|
private const val RESULT_OVERRIDE_DATA = 4
|
||||||
|
|
||||||
fun getBinderBackdoor(binder: IBinder): IBinder? {
|
fun getBinderBackdoor(binder: IBinder): IBinder? {
|
||||||
val data = Parcel.obtain()
|
val data = Parcel.obtain()
|
||||||
val reply = Parcel.obtain()
|
val reply = Parcel.obtain()
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
|
val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
|
||||||
if (success) {
|
if (success) {
|
||||||
Logger.d("Backdoor access granted for binder: $binder")
|
Logger.d("Backdoor access granted for binder: $binder")
|
||||||
reply.readStrongBinder()
|
reply.readStrongBinder()
|
||||||
} else {
|
} else {
|
||||||
Logger.d("Backdoor access denied for binder: $binder")
|
Logger.d("Backdoor access denied for binder: $binder")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e("Failed to access binder backdoor", e)
|
Logger.e("Failed to access binder backdoor", e)
|
||||||
null
|
null
|
||||||
} finally {
|
} finally {
|
||||||
data.recycle()
|
data.recycle()
|
||||||
reply.recycle()
|
reply.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerBinderInterceptor(
|
fun registerBinderInterceptor(
|
||||||
backdoor: IBinder,
|
backdoor: IBinder,
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
interceptor: BinderInterceptor
|
interceptor: BinderInterceptor
|
||||||
) {
|
) {
|
||||||
val data = Parcel.obtain()
|
val data = Parcel.obtain()
|
||||||
val reply = Parcel.obtain()
|
val reply = Parcel.obtain()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data.writeStrongBinder(target)
|
data.writeStrongBinder(target)
|
||||||
data.writeStrongBinder(interceptor)
|
data.writeStrongBinder(interceptor)
|
||||||
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||||
Logger.d("Registered interceptor for target: $target")
|
Logger.d("Registered interceptor for target: $target")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e("Failed to register binder interceptor", e)
|
Logger.e("Failed to register binder interceptor", e)
|
||||||
} finally {
|
} finally {
|
||||||
data.recycle()
|
data.recycle()
|
||||||
reply.recycle()
|
reply.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onPreTransact(
|
open fun onPreTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel
|
data: Parcel
|
||||||
): Result = Skip
|
): Result = Skip
|
||||||
|
|
||||||
open fun onPostTransact(
|
open fun onPostTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
reply: Parcel?,
|
reply: Parcel?,
|
||||||
resultCode: Int
|
resultCode: Int
|
||||||
): Result = Skip
|
): Result = Skip
|
||||||
|
|
||||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||||
val result = when (code) {
|
val result = when (code) {
|
||||||
PRE_TRANSACT_CODE -> handlePreTransact(data)
|
PRE_TRANSACT_CODE -> handlePreTransact(data)
|
||||||
POST_TRANSACT_CODE -> handlePostTransact(data)
|
POST_TRANSACT_CODE -> handlePostTransact(data)
|
||||||
else -> return super.onTransact(code, data, reply, flags)
|
else -> return super.onTransact(code, data, reply, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeResultToReply(result, reply!!)
|
writeResultToReply(result, reply!!)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePreTransact(data: Parcel): Result {
|
private fun handlePreTransact(data: Parcel): Result {
|
||||||
val target = data.readStrongBinder()
|
val target = data.readStrongBinder()
|
||||||
val transactionCode = data.readInt()
|
val transactionCode = data.readInt()
|
||||||
val transactionFlags = data.readInt()
|
val transactionFlags = data.readInt()
|
||||||
val callingUid = data.readInt()
|
val callingUid = data.readInt()
|
||||||
val callingPid = data.readInt()
|
val callingPid = data.readInt()
|
||||||
val dataSize = data.readLong()
|
val dataSize = data.readLong()
|
||||||
|
|
||||||
val transactionData = Parcel.obtain()
|
val transactionData = Parcel.obtain()
|
||||||
return try {
|
return try {
|
||||||
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
|
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
|
||||||
transactionData.setDataPosition(0)
|
transactionData.setDataPosition(0)
|
||||||
onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
|
onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
|
||||||
} finally {
|
} finally {
|
||||||
transactionData.recycle()
|
transactionData.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePostTransact(data: Parcel): Result {
|
private fun handlePostTransact(data: Parcel): Result {
|
||||||
val target = data.readStrongBinder()
|
val target = data.readStrongBinder()
|
||||||
val transactionCode = data.readInt()
|
val transactionCode = data.readInt()
|
||||||
val transactionFlags = data.readInt()
|
val transactionFlags = data.readInt()
|
||||||
val callingUid = data.readInt()
|
val callingUid = data.readInt()
|
||||||
val callingPid = data.readInt()
|
val callingPid = data.readInt()
|
||||||
val resultCode = data.readInt()
|
val resultCode = data.readInt()
|
||||||
|
|
||||||
val transactionData = Parcel.obtain()
|
val transactionData = Parcel.obtain()
|
||||||
val transactionReply = Parcel.obtain()
|
val transactionReply = Parcel.obtain()
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val dataSize = data.readLong().toInt()
|
val dataSize = data.readLong().toInt()
|
||||||
transactionData.appendFrom(data, data.dataPosition(), dataSize)
|
transactionData.appendFrom(data, data.dataPosition(), dataSize)
|
||||||
transactionData.setDataPosition(0)
|
transactionData.setDataPosition(0)
|
||||||
data.setDataPosition(data.dataPosition() + dataSize)
|
data.setDataPosition(data.dataPosition() + dataSize)
|
||||||
|
|
||||||
val replySize = data.readLong().toInt()
|
val replySize = data.readLong().toInt()
|
||||||
val reply = if (replySize > 0) {
|
val reply = if (replySize > 0) {
|
||||||
transactionReply.appendFrom(data, data.dataPosition(), replySize)
|
transactionReply.appendFrom(data, data.dataPosition(), replySize)
|
||||||
transactionReply.setDataPosition(0)
|
transactionReply.setDataPosition(0)
|
||||||
transactionReply
|
transactionReply
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
|
onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
|
||||||
} finally {
|
} finally {
|
||||||
transactionData.recycle()
|
transactionData.recycle()
|
||||||
transactionReply.recycle()
|
transactionReply.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeResultToReply(result: Result, reply: Parcel) {
|
private fun writeResultToReply(result: Result, reply: Parcel) {
|
||||||
when (result) {
|
when (result) {
|
||||||
Skip -> reply.writeInt(RESULT_SKIP)
|
Skip -> reply.writeInt(RESULT_SKIP)
|
||||||
Continue -> reply.writeInt(RESULT_CONTINUE)
|
Continue -> reply.writeInt(RESULT_CONTINUE)
|
||||||
is OverrideReply -> {
|
is OverrideReply -> {
|
||||||
reply.writeInt(RESULT_OVERRIDE_REPLY)
|
reply.writeInt(RESULT_OVERRIDE_REPLY)
|
||||||
reply.writeInt(result.code)
|
reply.writeInt(result.code)
|
||||||
reply.writeLong(result.reply.dataSize().toLong())
|
reply.writeLong(result.reply.dataSize().toLong())
|
||||||
reply.appendFrom(result.reply, 0, result.reply.dataSize())
|
reply.appendFrom(result.reply, 0, result.reply.dataSize())
|
||||||
result.reply.recycle()
|
result.reply.recycle()
|
||||||
}
|
}
|
||||||
is OverrideData -> {
|
is OverrideData -> {
|
||||||
reply.writeInt(RESULT_OVERRIDE_DATA)
|
reply.writeInt(RESULT_OVERRIDE_DATA)
|
||||||
reply.writeLong(result.data.dataSize().toLong())
|
reply.writeLong(result.data.dataSize().toLong())
|
||||||
reply.appendFrom(result.data, 0, result.data.dataSize())
|
reply.appendFrom(result.data, 0, result.data.dataSize())
|
||||||
result.data.recycle()
|
result.data.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+141
-141
@@ -1,142 +1,142 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||||
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.os.ServiceManager
|
import android.os.ServiceManager
|
||||||
import android.security.KeyStore
|
import android.security.KeyStore
|
||||||
import android.security.keystore.KeystoreResponse
|
import android.security.keystore.KeystoreResponse
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
abstract class BaseKeystoreInterceptor : BinderInterceptor() {
|
abstract class BaseKeystoreInterceptor : BinderInterceptor() {
|
||||||
|
|
||||||
protected lateinit var keystore: IBinder
|
protected lateinit var keystore: IBinder
|
||||||
protected var triedCount = 0
|
protected var triedCount = 0
|
||||||
protected var injected = false
|
protected var injected = false
|
||||||
protected open val maxRetries: Int = 3
|
protected open val maxRetries: Int = 3
|
||||||
|
|
||||||
protected abstract val serviceName: String
|
protected abstract val serviceName: String
|
||||||
protected abstract val injectionCommand: String
|
protected abstract val injectionCommand: String
|
||||||
protected abstract val processName: String
|
protected abstract val processName: String
|
||||||
|
|
||||||
fun tryRunKeystoreInterceptor(): Boolean {
|
fun tryRunKeystoreInterceptor(): Boolean {
|
||||||
Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
|
Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
|
||||||
|
|
||||||
val service = getService() ?: return false
|
val service = getService() ?: return false
|
||||||
val backdoor = getBinderBackdoor(service)
|
val backdoor = getBinderBackdoor(service)
|
||||||
|
|
||||||
return if (backdoor != null) {
|
return if (backdoor != null) {
|
||||||
setupInterceptor(service, backdoor)
|
setupInterceptor(service, backdoor)
|
||||||
} else {
|
} else {
|
||||||
handleMissingBackdoor()
|
handleMissingBackdoor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
|
protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
|
||||||
|
|
||||||
protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
|
protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
|
||||||
keystore = service
|
keystore = service
|
||||||
Logger.i("Registering for $serviceName: $keystore")
|
Logger.i("Registering for $serviceName: $keystore")
|
||||||
|
|
||||||
registerBinderInterceptor(backdoor, service, this)
|
registerBinderInterceptor(backdoor, service, this)
|
||||||
service.linkToDeath(createDeathRecipient(), 0)
|
service.linkToDeath(createDeathRecipient(), 0)
|
||||||
onInterceptorSetup(service, backdoor)
|
onInterceptorSetup(service, backdoor)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMissingBackdoor(): Boolean {
|
private fun handleMissingBackdoor(): Boolean {
|
||||||
if (triedCount >= maxRetries) {
|
if (triedCount >= maxRetries) {
|
||||||
Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
|
Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!injected) {
|
if (!injected) {
|
||||||
performInjection()
|
performInjection()
|
||||||
injected = true
|
injected = true
|
||||||
}
|
}
|
||||||
|
|
||||||
triedCount++
|
triedCount++
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun performInjection() {
|
protected open fun performInjection() {
|
||||||
Logger.i("Attempting to inject into $processName...")
|
Logger.i("Attempting to inject into $processName...")
|
||||||
|
|
||||||
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
|
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
|
||||||
Logger.d("Injection command: ${command.joinToString(" ")}")
|
Logger.d("Injection command: ${command.joinToString(" ")}")
|
||||||
|
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val process = Runtime.getRuntime().exec(command)
|
||||||
|
|
||||||
if (process.waitFor() != 0) {
|
if (process.waitFor() != 0) {
|
||||||
Logger.e("Injection failed! Daemon will exit")
|
Logger.e("Injection failed! Daemon will exit")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.i("Injection completed successfully")
|
Logger.i("Injection completed successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
|
protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
|
||||||
override fun binderDied() {
|
override fun binderDied() {
|
||||||
Logger.d("$serviceName died, daemon restarting")
|
Logger.d("$serviceName died, daemon restarting")
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
||||||
// Default implementation does nothing
|
// Default implementation does nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object InterceptorUtils {
|
object InterceptorUtils {
|
||||||
|
|
||||||
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
||||||
val parcel = Parcel.obtain()
|
val parcel = Parcel.obtain()
|
||||||
try {
|
try {
|
||||||
parcel.writeInt(KeyStore.NO_ERROR)
|
parcel.writeInt(KeyStore.NO_ERROR)
|
||||||
parcel.writeString("")
|
parcel.writeString("")
|
||||||
parcel.setDataPosition(0)
|
parcel.setDataPosition(0)
|
||||||
return KeystoreResponse.CREATOR.createFromParcel(parcel)
|
return KeystoreResponse.CREATOR.createFromParcel(parcel)
|
||||||
} finally {
|
} finally {
|
||||||
parcel.recycle()
|
parcel.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
||||||
val parcel = Parcel.obtain()
|
val parcel = Parcel.obtain()
|
||||||
parcel.writeNoException()
|
parcel.writeNoException()
|
||||||
parcel.writeInt(resultCode)
|
parcel.writeInt(resultCode)
|
||||||
return BinderInterceptor.OverrideReply(0, parcel)
|
return BinderInterceptor.OverrideReply(0, parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
||||||
val parcel = Parcel.obtain()
|
val parcel = Parcel.obtain()
|
||||||
parcel.writeNoException()
|
parcel.writeNoException()
|
||||||
parcel.writeByteArray(data)
|
parcel.writeByteArray(data)
|
||||||
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Parcelable?> createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
|
fun <T : Parcelable?> createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
|
||||||
val parcel = Parcel.obtain()
|
val parcel = Parcel.obtain()
|
||||||
parcel.writeNoException()
|
parcel.writeNoException()
|
||||||
parcel.writeTypedObject(obj, flags)
|
parcel.writeTypedObject(obj, flags)
|
||||||
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.extractAlias(): String {
|
fun String.extractAlias(): String {
|
||||||
return when {
|
return when {
|
||||||
contains("_") -> split("_")[1]
|
contains("_") -> split("_")[1]
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Parcel.hasException(): Boolean {
|
fun Parcel.hasException(): Boolean {
|
||||||
return kotlin.runCatching { readException() }.exceptionOrNull() != null
|
return kotlin.runCatching { readException() }.exceptionOrNull() != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+157
-157
@@ -1,158 +1,158 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.hardware.security.keymint.SecurityLevel
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.system.keystore2.IKeystoreService
|
import android.system.keystore2.IKeystoreService
|
||||||
import android.system.keystore2.KeyDescriptor
|
import android.system.keystore2.KeyDescriptor
|
||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||||
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
|
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
|
||||||
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
|
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
|
||||||
|
|
||||||
@SuppressLint("BlockedPrivateApi")
|
@SuppressLint("BlockedPrivateApi")
|
||||||
object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||||
private val getKeyEntryTransaction =
|
private val getKeyEntryTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
|
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
|
||||||
private val deleteKeyTransaction =
|
private val deleteKeyTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
|
getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
|
||||||
|
|
||||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||||
override val processName = "keystore2"
|
override val processName = "keystore2"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
|
||||||
|
|
||||||
private var teeInterceptor: SecurityLevelInterceptor? = null
|
private var teeInterceptor: SecurityLevelInterceptor? = null
|
||||||
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
|
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
|
||||||
|
|
||||||
override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
||||||
setupSecurityLevelInterceptors(service, backdoor)
|
setupSecurityLevelInterceptors(service, backdoor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
|
private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
|
||||||
val ks = IKeystoreService.Stub.asInterface(service)
|
val ks = IKeystoreService.Stub.asInterface(service)
|
||||||
|
|
||||||
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
|
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
if (tee != null) {
|
if (tee != null) {
|
||||||
Logger.i("Registering for TEE SecurityLevel: $tee")
|
Logger.i("Registering for TEE SecurityLevel: $tee")
|
||||||
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||||
registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
|
registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
|
||||||
teeInterceptor = interceptor
|
teeInterceptor = interceptor
|
||||||
} else {
|
} else {
|
||||||
Logger.i("No TEE SecurityLevel found")
|
Logger.i("No TEE SecurityLevel found")
|
||||||
}
|
}
|
||||||
|
|
||||||
val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
|
val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
if (strongBox != null) {
|
if (strongBox != null) {
|
||||||
Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
|
Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
|
||||||
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
|
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
|
||||||
registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
|
registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
|
||||||
strongBoxInterceptor = interceptor
|
strongBoxInterceptor = interceptor
|
||||||
} else {
|
} else {
|
||||||
Logger.i("No StrongBox SecurityLevel found")
|
Logger.i("No StrongBox SecurityLevel found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreTransact(
|
override fun onPreTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel
|
data: Parcel
|
||||||
): Result {
|
): Result {
|
||||||
if (code == getKeyEntryTransaction) {
|
if (code == getKeyEntryTransaction) {
|
||||||
if (CertificateHacker.canHack()) {
|
if (CertificateHacker.canHack()) {
|
||||||
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
|
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||||
if (Config.needGenerate(callingUid)) {
|
if (Config.needGenerate(callingUid)) {
|
||||||
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||||
?: return@runCatching
|
?: return@runCatching
|
||||||
Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
|
Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
|
||||||
return createTypedObjectReply(response)
|
return createTypedObjectReply(response)
|
||||||
} else if (Config.needHack(callingUid)) {
|
} else if (Config.needHack(callingUid)) {
|
||||||
if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
|
if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
|
||||||
Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
|
Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
|
||||||
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}")
|
Logger.i("Found generated response for uid=$callingUid alias=${descriptor.alias}")
|
||||||
return createTypedObjectReply(response)
|
return createTypedObjectReply(response)
|
||||||
} else {
|
} else {
|
||||||
Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}")
|
Logger.e("No generated response found for uid=$callingUid alias=${descriptor.alias}")
|
||||||
return@runCatching
|
return@runCatching
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}")
|
Logger.i("proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}")
|
||||||
return Continue
|
return Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostTransact(
|
override fun onPostTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
reply: Parcel?,
|
reply: Parcel?,
|
||||||
resultCode: Int
|
resultCode: Int
|
||||||
): Result {
|
): Result {
|
||||||
if (target != keystore || reply == null) return Skip
|
if (target != keystore || reply == null) return Skip
|
||||||
if (reply.hasException()) return Skip
|
if (reply.hasException()) return Skip
|
||||||
val p = Parcel.obtain()
|
val p = Parcel.obtain()
|
||||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||||
|
|
||||||
if (code == deleteKeyTransaction && resultCode == 0) {
|
if (code == deleteKeyTransaction && resultCode == 0) {
|
||||||
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
||||||
|
|
||||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
|
if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
|
||||||
|
|
||||||
SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias))
|
SecurityLevelInterceptor.keys.remove(SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias))
|
||||||
|
|
||||||
return Skip
|
return Skip
|
||||||
} else if (code == getKeyEntryTransaction) {
|
} else if (code == getKeyEntryTransaction) {
|
||||||
try {
|
try {
|
||||||
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
||||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
val chain = CertificateUtils.run { response.getCertificateChain() }
|
val chain = CertificateUtils.run { response.getCertificateChain() }
|
||||||
if (chain != null) {
|
if (chain != null) {
|
||||||
val newChain = CertificateHacker.hackCertificateChain(chain)
|
val newChain = CertificateHacker.hackCertificateChain(chain)
|
||||||
response.putCertificateChain(newChain).getOrThrow()
|
response.putCertificateChain(newChain).getOrThrow()
|
||||||
Logger.i("Hacked certificate for uid=$callingUid")
|
Logger.i("Hacked certificate for uid=$callingUid")
|
||||||
return createTypedObjectReply(response)
|
return createTypedObjectReply(response)
|
||||||
} else {
|
} else {
|
||||||
p.recycle()
|
p.recycle()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.recycle()
|
p.recycle()
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
||||||
p.recycle()
|
p.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+237
-237
@@ -1,238 +1,238 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.security.Credentials
|
import android.security.Credentials
|
||||||
import android.security.KeyStore
|
import android.security.KeyStore
|
||||||
import android.security.keymaster.ExportResult
|
import android.security.keymaster.ExportResult
|
||||||
import android.security.keymaster.KeyCharacteristics
|
import android.security.keymaster.KeyCharacteristics
|
||||||
import android.security.keymaster.KeymasterArguments
|
import android.security.keymaster.KeymasterArguments
|
||||||
import android.security.keymaster.KeymasterCertificateChain
|
import android.security.keymaster.KeymasterCertificateChain
|
||||||
import android.security.keymaster.KeymasterDefs
|
import android.security.keymaster.KeymasterDefs
|
||||||
import android.security.keystore.IKeystoreCertificateChainCallback
|
import android.security.keystore.IKeystoreCertificateChainCallback
|
||||||
import android.security.keystore.IKeystoreExportKeyCallback
|
import android.security.keystore.IKeystoreExportKeyCallback
|
||||||
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
|
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
|
||||||
import android.security.keystore.IKeystoreService
|
import android.security.keystore.IKeystoreService
|
||||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
|
||||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
|
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@SuppressLint("BlockedPrivateApi")
|
@SuppressLint("BlockedPrivateApi")
|
||||||
object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||||
private val getTransaction =
|
private val getTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "get")
|
getTransactCode(IKeystoreService.Stub::class.java, "get")
|
||||||
private val generateKeyTransaction =
|
private val generateKeyTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
|
getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
|
||||||
private val getKeyCharacteristicsTransaction =
|
private val getKeyCharacteristicsTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
|
getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
|
||||||
private val exportKeyTransaction =
|
private val exportKeyTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
|
getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
|
||||||
private val attestKeyTransaction =
|
private val attestKeyTransaction =
|
||||||
getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
|
getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
|
||||||
|
|
||||||
override val serviceName = "android.security.keystore"
|
override val serviceName = "android.security.keystore"
|
||||||
override val processName = "keystore"
|
override val processName = "keystore"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
|
||||||
|
|
||||||
private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
|
private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
|
||||||
|
|
||||||
private val keyArguments = HashMap<Key, CertificateHacker.KeyGenParameters>()
|
private val keyArguments = HashMap<Key, CertificateHacker.KeyGenParameters>()
|
||||||
private val keyPairs = HashMap<Key, KeyPair>()
|
private val keyPairs = HashMap<Key, KeyPair>()
|
||||||
|
|
||||||
data class Key(val uid: Int, val alias: String)
|
data class Key(val uid: Int, val alias: String)
|
||||||
|
|
||||||
override fun onPreTransact(
|
override fun onPreTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel
|
data: Parcel
|
||||||
): Result {
|
): Result {
|
||||||
if (CertificateHacker.canHack()) {
|
if (CertificateHacker.canHack()) {
|
||||||
if (code == getTransaction) {
|
if (code == getTransaction) {
|
||||||
if (Config.needHack(callingUid)) {
|
if (Config.needHack(callingUid)) {
|
||||||
return Continue
|
return Continue
|
||||||
} else if (Config.needGenerate(callingUid)) {
|
} else if (Config.needGenerate(callingUid)) {
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
} else if (Config.needGenerate(callingUid)) {
|
} else if (Config.needGenerate(callingUid)) {
|
||||||
when (code) {
|
when (code) {
|
||||||
generateKeyTransaction -> {
|
generateKeyTransaction -> {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
data.enforceInterface(DESCRIPTOR)
|
data.enforceInterface(DESCRIPTOR)
|
||||||
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
val alias = data.readString()!!.extractAlias()
|
val alias = data.readString()!!.extractAlias()
|
||||||
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
|
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
|
||||||
val check = data.readInt()
|
val check = data.readInt()
|
||||||
val kma = KeymasterArguments()
|
val kma = KeymasterArguments()
|
||||||
val kgp = CertificateHacker.KeyGenParameters()
|
val kgp = CertificateHacker.KeyGenParameters()
|
||||||
if (check == 1) {
|
if (check == 1) {
|
||||||
kma.readFromParcel(data)
|
kma.readFromParcel(data)
|
||||||
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||||
kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||||
kgp.setEcCurveName(kgp.keySize)
|
kgp.setEcCurveName(kgp.keySize)
|
||||||
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
|
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
|
||||||
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
|
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
|
||||||
kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
|
kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
|
||||||
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
|
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
|
||||||
try {
|
try {
|
||||||
val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
|
val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
|
||||||
getArgumentByTag.isAccessible = true
|
getArgumentByTag.isAccessible = true
|
||||||
val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
||||||
|
|
||||||
val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
|
val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
|
||||||
getLongTagValue.isAccessible = true
|
getLongTagValue.isAccessible = true
|
||||||
kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
|
kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Logger.e("Read rsaPublicExponent error", ex)
|
Logger.e("Read rsaPublicExponent error", ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyArguments[Key(callingUid, alias)] = kgp
|
keyArguments[Key(callingUid, alias)] = kgp
|
||||||
}
|
}
|
||||||
|
|
||||||
val kc = KeyCharacteristics()
|
val kc = KeyCharacteristics()
|
||||||
kc.swEnforced = KeymasterArguments()
|
kc.swEnforced = KeymasterArguments()
|
||||||
kc.hwEnforced = kma
|
kc.hwEnforced = kma
|
||||||
|
|
||||||
val ksr = createSuccessKeystoreResponse()
|
val ksr = createSuccessKeystoreResponse()
|
||||||
callback.onFinished(ksr, kc)
|
callback.onFinished(ksr, kc)
|
||||||
|
|
||||||
return createSuccessReply()
|
return createSuccessReply()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("generateKeyTransaction error", it)
|
Logger.e("generateKeyTransaction error", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyCharacteristicsTransaction -> {
|
getKeyCharacteristicsTransaction -> {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
data.enforceInterface(DESCRIPTOR)
|
data.enforceInterface(DESCRIPTOR)
|
||||||
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
val alias = data.readString()!!.extractAlias()
|
val alias = data.readString()!!.extractAlias()
|
||||||
Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
|
Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
|
||||||
val kc = KeyCharacteristics()
|
val kc = KeyCharacteristics()
|
||||||
val kma = KeymasterArguments()
|
val kma = KeymasterArguments()
|
||||||
kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
|
kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
|
||||||
kc.swEnforced = KeymasterArguments()
|
kc.swEnforced = KeymasterArguments()
|
||||||
kc.hwEnforced = kma
|
kc.hwEnforced = kma
|
||||||
|
|
||||||
val ksr = createSuccessKeystoreResponse()
|
val ksr = createSuccessKeystoreResponse()
|
||||||
callback.onFinished(ksr, kc)
|
callback.onFinished(ksr, kc)
|
||||||
|
|
||||||
return createSuccessReply()
|
return createSuccessReply()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("getKeyCharacteristicsTransaction error", it)
|
Logger.e("getKeyCharacteristicsTransaction error", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exportKeyTransaction -> {
|
exportKeyTransaction -> {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
data.enforceInterface(DESCRIPTOR)
|
data.enforceInterface(DESCRIPTOR)
|
||||||
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
val alias = data.readString()!!.extractAlias()
|
val alias = data.readString()!!.extractAlias()
|
||||||
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
|
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
|
||||||
val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
|
val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
|
||||||
keyPairs[Key(callingUid, alias)] = kp!!
|
keyPairs[Key(callingUid, alias)] = kp!!
|
||||||
|
|
||||||
val erP = Parcel.obtain()
|
val erP = Parcel.obtain()
|
||||||
erP.writeInt(KeyStore.NO_ERROR)
|
erP.writeInt(KeyStore.NO_ERROR)
|
||||||
erP.writeByteArray(kp.public.encoded)
|
erP.writeByteArray(kp.public.encoded)
|
||||||
erP.setDataPosition(0)
|
erP.setDataPosition(0)
|
||||||
val er = ExportResult.CREATOR.createFromParcel(erP)
|
val er = ExportResult.CREATOR.createFromParcel(erP)
|
||||||
erP.recycle()
|
erP.recycle()
|
||||||
|
|
||||||
callback.onFinished(er)
|
callback.onFinished(er)
|
||||||
|
|
||||||
return createSuccessReply()
|
return createSuccessReply()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("exportKeyTransaction error", it)
|
Logger.e("exportKeyTransaction error", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attestKeyTransaction -> {
|
attestKeyTransaction -> {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
data.enforceInterface(DESCRIPTOR)
|
data.enforceInterface(DESCRIPTOR)
|
||||||
val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
val alias = data.readString()!!.extractAlias()
|
val alias = data.readString()!!.extractAlias()
|
||||||
Logger.i("attestKeyTransaction uid $callingUid alias $alias")
|
Logger.i("attestKeyTransaction uid $callingUid alias $alias")
|
||||||
val check = data.readInt()
|
val check = data.readInt()
|
||||||
val kma = KeymasterArguments()
|
val kma = KeymasterArguments()
|
||||||
if (check == 1) {
|
if (check == 1) {
|
||||||
kma.readFromParcel(data)
|
kma.readFromParcel(data)
|
||||||
val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
|
val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
|
||||||
|
|
||||||
val ksr = createSuccessKeystoreResponse()
|
val ksr = createSuccessKeystoreResponse()
|
||||||
|
|
||||||
val key = Key(callingUid, alias)
|
val key = Key(callingUid, alias)
|
||||||
val ka = keyArguments[key]!!
|
val ka = keyArguments[key]!!
|
||||||
ka.attestationChallenge = attestationChallenge
|
ka.attestationChallenge = attestationChallenge
|
||||||
val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
|
val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
|
||||||
|
|
||||||
val kcc = KeymasterCertificateChain(chain)
|
val kcc = KeymasterCertificateChain(chain)
|
||||||
callback.onFinished(ksr, kcc)
|
callback.onFinished(ksr, kcc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createSuccessReply()
|
return createSuccessReply()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("attestKeyTransaction error", it)
|
Logger.e("attestKeyTransaction error", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostTransact(
|
override fun onPostTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
reply: Parcel?,
|
reply: Parcel?,
|
||||||
resultCode: Int
|
resultCode: Int
|
||||||
): Result {
|
): Result {
|
||||||
if (target != keystore || code != getTransaction || reply == null) return Skip
|
if (target != keystore || code != getTransaction || reply == null) return Skip
|
||||||
if (reply.hasException()) return Skip
|
if (reply.hasException()) return Skip
|
||||||
val p = Parcel.obtain()
|
val p = Parcel.obtain()
|
||||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||||
try {
|
try {
|
||||||
data.enforceInterface(DESCRIPTOR)
|
data.enforceInterface(DESCRIPTOR)
|
||||||
val alias = data.readString() ?: ""
|
val alias = data.readString() ?: ""
|
||||||
var response = reply.createByteArray()
|
var response = reply.createByteArray()
|
||||||
when {
|
when {
|
||||||
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
|
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
|
||||||
response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid)
|
response = CertificateHacker.hackCertificateChainUSR(response!!, alias.extractAlias(), callingUid)
|
||||||
Logger.i("Hacked leaf certificate for uid=$callingUid")
|
Logger.i("Hacked leaf certificate for uid=$callingUid")
|
||||||
return createByteArrayReply(response)
|
return createByteArrayReply(response)
|
||||||
}
|
}
|
||||||
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
|
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
|
||||||
response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid)
|
response = CertificateHacker.hackCertificateChainCA(response!!, alias.extractAlias(), callingUid)
|
||||||
Logger.i("Hacked CA certificate chain for uid=$callingUid")
|
Logger.i("Hacked CA certificate chain for uid=$callingUid")
|
||||||
return createByteArrayReply(response)
|
return createByteArrayReply(response)
|
||||||
}
|
}
|
||||||
else -> p.recycle()
|
else -> p.recycle()
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
|
||||||
p.recycle()
|
p.recycle()
|
||||||
}
|
}
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+179
-179
@@ -1,180 +1,180 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
package io.github.beakthoven.TrickyStoreOSS.interceptors
|
||||||
|
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
import android.hardware.security.keymint.KeyParameterValue
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
import android.hardware.security.keymint.Tag
|
import android.hardware.security.keymint.Tag
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.system.keystore2.Authorization
|
import android.system.keystore2.Authorization
|
||||||
import android.system.keystore2.IKeystoreSecurityLevel
|
import android.system.keystore2.IKeystoreSecurityLevel
|
||||||
import android.system.keystore2.KeyDescriptor
|
import android.system.keystore2.KeyDescriptor
|
||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
import android.system.keystore2.KeyMetadata
|
import android.system.keystore2.KeyMetadata
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
|
||||||
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
|
||||||
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
|
||||||
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
|
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class SecurityLevelInterceptor(
|
class SecurityLevelInterceptor(
|
||||||
private val original: IKeystoreSecurityLevel,
|
private val original: IKeystoreSecurityLevel,
|
||||||
private val level: Int
|
private val level: Int
|
||||||
) : BinderInterceptor() {
|
) : BinderInterceptor() {
|
||||||
companion object {
|
companion object {
|
||||||
private val generateKeyTransaction =
|
private val generateKeyTransaction =
|
||||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||||
private val deleteKeyTransaction =
|
private val deleteKeyTransaction =
|
||||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
|
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
|
||||||
private val createOperationTransaction =
|
private val createOperationTransaction =
|
||||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
|
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
val keys = ConcurrentHashMap<Key, Info>()
|
val keys = ConcurrentHashMap<Key, Info>()
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
|
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
|
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
|
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
|
||||||
keys[Key(uid, alias)]?.response
|
keys[Key(uid, alias)]?.response
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
|
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
|
||||||
keyPairs[Key(uid, alias)]
|
keyPairs[Key(uid, alias)]
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
|
fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
|
||||||
skipLeafHacks[Key(uid, alias)] ?: false
|
skipLeafHacks[Key(uid, alias)] ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Key(val uid: Int, val alias: String)
|
data class Key(val uid: Int, val alias: String)
|
||||||
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
|
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
|
||||||
|
|
||||||
override fun onPreTransact(
|
override fun onPreTransact(
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
code: Int,
|
code: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel
|
data: Parcel
|
||||||
): Result {
|
): Result {
|
||||||
if (code == generateKeyTransaction) {
|
if (code == generateKeyTransaction) {
|
||||||
Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
|
Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val keyDescriptor =
|
val keyDescriptor =
|
||||||
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||||
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
val aFlags = data.readInt()
|
val aFlags = data.readInt()
|
||||||
val entropy = data.createByteArray()
|
val entropy = data.createByteArray()
|
||||||
val kgp = CertificateHacker.KeyGenParameters(params)
|
val kgp = CertificateHacker.KeyGenParameters(params)
|
||||||
if (Config.needGenerate(callingUid)) {
|
if (Config.needGenerate(callingUid)) {
|
||||||
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
|
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
|
||||||
?: return@runCatching
|
?: return@runCatching
|
||||||
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
||||||
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
||||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||||
val p = Parcel.obtain()
|
val p = Parcel.obtain()
|
||||||
p.writeNoException()
|
p.writeNoException()
|
||||||
p.writeTypedObject(response.metadata, 0)
|
p.writeTypedObject(response.metadata, 0)
|
||||||
return OverrideReply(0, p)
|
return OverrideReply(0, p)
|
||||||
} else if (Config.needHack(callingUid)) {
|
} else if (Config.needHack(callingUid)) {
|
||||||
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
|
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
|
||||||
Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
|
Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
|
||||||
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
|
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp)
|
||||||
?: return@runCatching
|
?: return@runCatching
|
||||||
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
||||||
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
||||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||||
SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
|
SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
|
||||||
val p = Parcel.obtain()
|
val p = Parcel.obtain()
|
||||||
p.writeNoException()
|
p.writeNoException()
|
||||||
p.writeTypedObject(response.metadata, 0)
|
p.writeTypedObject(response.metadata, 0)
|
||||||
return OverrideReply(0, p)
|
return OverrideReply(0, p)
|
||||||
} else {
|
} else {
|
||||||
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
|
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
|
||||||
Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
|
Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Logger.e("parse key gen request", it)
|
Logger.e("parse key gen request", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Skip
|
return Skip
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildResponse(
|
private fun buildResponse(
|
||||||
chain: List<Certificate>,
|
chain: List<Certificate>,
|
||||||
params: CertificateHacker.KeyGenParameters,
|
params: CertificateHacker.KeyGenParameters,
|
||||||
descriptor: KeyDescriptor
|
descriptor: KeyDescriptor
|
||||||
): KeyEntryResponse {
|
): KeyEntryResponse {
|
||||||
val response = KeyEntryResponse()
|
val response = KeyEntryResponse()
|
||||||
val metadata = KeyMetadata()
|
val metadata = KeyMetadata()
|
||||||
metadata.keySecurityLevel = level
|
metadata.keySecurityLevel = level
|
||||||
metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
|
metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
|
||||||
val d = KeyDescriptor()
|
val d = KeyDescriptor()
|
||||||
d.domain = descriptor.domain
|
d.domain = descriptor.domain
|
||||||
d.nspace = descriptor.nspace
|
d.nspace = descriptor.nspace
|
||||||
metadata.key = d
|
metadata.key = d
|
||||||
val authorizations = ArrayList<Authorization>()
|
val authorizations = ArrayList<Authorization>()
|
||||||
var a: Authorization
|
var a: Authorization
|
||||||
for (i in params.purpose.toList()) {
|
for (i in params.purpose.toList()) {
|
||||||
a = Authorization()
|
a = Authorization()
|
||||||
a.keyParameter = KeyParameter()
|
a.keyParameter = KeyParameter()
|
||||||
a.keyParameter.tag = Tag.PURPOSE
|
a.keyParameter.tag = Tag.PURPOSE
|
||||||
a.keyParameter.value = KeyParameterValue.keyPurpose(i)
|
a.keyParameter.value = KeyParameterValue.keyPurpose(i)
|
||||||
a.securityLevel = level
|
a.securityLevel = level
|
||||||
authorizations.add(a)
|
authorizations.add(a)
|
||||||
}
|
}
|
||||||
for (i in params.digest.toList()) {
|
for (i in params.digest.toList()) {
|
||||||
a = Authorization()
|
a = Authorization()
|
||||||
a.keyParameter = KeyParameter()
|
a.keyParameter = KeyParameter()
|
||||||
a.keyParameter.tag = Tag.DIGEST
|
a.keyParameter.tag = Tag.DIGEST
|
||||||
a.keyParameter.value = KeyParameterValue.digest(i)
|
a.keyParameter.value = KeyParameterValue.digest(i)
|
||||||
a.securityLevel = level
|
a.securityLevel = level
|
||||||
authorizations.add(a)
|
authorizations.add(a)
|
||||||
}
|
}
|
||||||
a = Authorization()
|
a = Authorization()
|
||||||
a.keyParameter = KeyParameter()
|
a.keyParameter = KeyParameter()
|
||||||
a.keyParameter.tag = Tag.ALGORITHM
|
a.keyParameter.tag = Tag.ALGORITHM
|
||||||
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
|
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
|
||||||
a.securityLevel = level
|
a.securityLevel = level
|
||||||
authorizations.add(a)
|
authorizations.add(a)
|
||||||
a = Authorization()
|
a = Authorization()
|
||||||
a.keyParameter = KeyParameter()
|
a.keyParameter = KeyParameter()
|
||||||
a.keyParameter.tag = Tag.KEY_SIZE
|
a.keyParameter.tag = Tag.KEY_SIZE
|
||||||
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
|
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
|
||||||
a.securityLevel = level
|
a.securityLevel = level
|
||||||
authorizations.add(a)
|
authorizations.add(a)
|
||||||
a = Authorization()
|
a = Authorization()
|
||||||
a.keyParameter = KeyParameter()
|
a.keyParameter = KeyParameter()
|
||||||
a.keyParameter.tag = Tag.EC_CURVE
|
a.keyParameter.tag = Tag.EC_CURVE
|
||||||
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
|
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
|
||||||
a.securityLevel = level
|
a.securityLevel = level
|
||||||
authorizations.add(a)
|
authorizations.add(a)
|
||||||
a = Authorization()
|
a = Authorization()
|
||||||
a.keyParameter = KeyParameter()
|
a.keyParameter = KeyParameter()
|
||||||
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
|
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
|
||||||
a.keyParameter.value = KeyParameterValue.boolValue(true)
|
a.keyParameter.value = KeyParameterValue.boolValue(true)
|
||||||
a.securityLevel = level
|
a.securityLevel = level
|
||||||
authorizations.add(a)
|
authorizations.add(a)
|
||||||
metadata.authorizations = authorizations.toTypedArray<Authorization>()
|
metadata.authorizations = authorizations.toTypedArray<Authorization>()
|
||||||
response.metadata = metadata
|
response.metadata = metadata
|
||||||
response.iSecurityLevel = original
|
response.iSecurityLevel = original
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-6
@@ -1,6 +1,6 @@
|
|||||||
#Mon Aug 04 09:32:06 IST 2025
|
#Mon Aug 04 09:32:06 IST 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Vendored
+89
-89
@@ -1,89 +1,89 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem Copyright 2015 the original author or authors.
|
@rem Copyright 2015 the original author or authors.
|
||||||
@rem
|
@rem
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
@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 not use this file except in compliance with the License.
|
||||||
@rem You may obtain a copy of the License at
|
@rem You may obtain a copy of the License at
|
||||||
@rem
|
@rem
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
@rem
|
@rem
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
@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 distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
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.
|
@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"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
echo.
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation.
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
echo.
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation.
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
exit /b 1
|
exit /b 1
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
:omega
|
:omega
|
||||||
|
|||||||
+114
-114
@@ -1,114 +1,114 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<AndroidAttestation>
|
<AndroidAttestation>
|
||||||
<NumberOfKeyboxes>1</NumberOfKeyboxes>
|
<NumberOfKeyboxes>1</NumberOfKeyboxes>
|
||||||
<Keybox DeviceID="sw">
|
<Keybox DeviceID="sw">
|
||||||
<Key algorithm="ecdsa">
|
<Key algorithm="ecdsa">
|
||||||
<PrivateKey format="pem">
|
<PrivateKey format="pem">
|
||||||
-----BEGIN EC PRIVATE KEY-----
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
|
MHcCAQEEICHghkMqFRmEWc82OlD8FMnarfk19SfC39ceTW28QuVEoAoGCCqGSM49
|
||||||
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
|
AwEHoUQDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6l
|
||||||
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
|
uU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFA==
|
||||||
-----END EC PRIVATE KEY-----
|
-----END EC PRIVATE KEY-----
|
||||||
</PrivateKey>
|
</PrivateKey>
|
||||||
<CertificateChain>
|
<CertificateChain>
|
||||||
<NumberOfCertificates>2</NumberOfCertificates>
|
<NumberOfCertificates>2</NumberOfCertificates>
|
||||||
<Certificate format="pem">
|
<Certificate format="pem">
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
|
MIICeDCCAh6gAwIBAgICEAEwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMw
|
||||||
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
|
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYD
|
||||||
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
|
VQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFu
|
||||||
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
|
ZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAx
|
||||||
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
|
MTEwMDQ2MDlaFw0yNjAxMDgwMDQ2MDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||||
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
|
CAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdB
|
||||||
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
|
bmRyb2lkMTswOQYDVQQDDDJBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVz
|
||||||
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
|
dGF0aW9uIEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOue
|
||||||
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
|
efhCY1msyyqRTImGzHCtkGaTgqlzJhP+rMv4ISdMIXSXSir+pblNf2bU4GUQZjW8
|
||||||
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
|
U7ego6ZxWD7bPhGuEBSjZjBkMB0GA1UdDgQWBBQ//KzWGrE6noEguNUlHMVlux6R
|
||||||
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
|
qTAfBgNVHSMEGDAWgBTIrel3TEXDo88NFhDkeUM6IVowzzASBgNVHRMBAf8ECDAG
|
||||||
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
|
AQH/AgEAMA4GA1UdDwEB/wQEAwIChDAKBggqhkjOPQQDAgNIADBFAiBLipt77oK8
|
||||||
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
|
wDOHri/AiZi03cONqycqRZ9pDMfDktQPjgIhAO7aAV229DLp1IQ7YkyUBO86fMy9
|
||||||
Xvsiu+f+uXc/WT/7
|
Xvsiu+f+uXc/WT/7
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
</Certificate>
|
</Certificate>
|
||||||
<Certificate format="pem">
|
<Certificate format="pem">
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
|
MIICizCCAjKgAwIBAgIJAKIFntEOQ1tXMAoGCCqGSM49BAMCMIGYMQswCQYDVQQG
|
||||||
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
|
EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmll
|
||||||
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
|
dzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYD
|
||||||
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
|
VQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3Qw
|
||||||
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
|
HhcNMTYwMTExMDA0MzUwWhcNMzYwMTA2MDA0MzUwWjCBmDELMAkGA1UEBhMCVVMx
|
||||||
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
||||||
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
|
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwq
|
||||||
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
|
QW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MFkwEwYH
|
||||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
|
KoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamguD/9/SQ59
|
||||||
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
|
dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpKNjMGEwHQYDVR0O
|
||||||
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
|
BBYEFMit6XdMRcOjzw0WEOR5QzohWjDPMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0W
|
||||||
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
|
EOR5QzohWjDPMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMAoGCCqG
|
||||||
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
|
SM49BAMCA0cAMEQCIDUho++LNEYenNVg8x1YiSBq3KNlQfYNns6KGYxmSGB7AiBN
|
||||||
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
|
C/NR2TB8fVvaNTQdqEcbY6WFZTytTySn502vQX3xvw==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
</Certificate>
|
</Certificate>
|
||||||
</CertificateChain>
|
</CertificateChain>
|
||||||
</Key>
|
</Key>
|
||||||
<Key algorithm="rsa">
|
<Key algorithm="rsa">
|
||||||
<PrivateKey format="pem">
|
<PrivateKey format="pem">
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
|
MIICXQIBAAKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1qEIEir6LR752/q7yXPKb
|
||||||
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
|
KvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX4YVBeuVKvClqOm21wAQI
|
||||||
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
|
O2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmdXX0jYvKcXgLocQIDAQAB
|
||||||
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
|
AoGBAL6GCwuZqAKm+xpZQ4p7txUGWwmjbcbpysxr88AsNNfXnpTGYGQo2Ix7f2V3
|
||||||
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
|
wc3qZAdKvo5yht8fCBHclygmCGjeldMu/Ja20IT/JxpfYN78xwPno45uKbqaPF/C
|
||||||
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
|
woB2tqiWrx0014gozpvdsfNPnJQEQweBKY4gExZyW728mTpBAkEA4cbZJ2RsCRbs
|
||||||
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
|
NoJtWUmDdAwh8bB0xKGlmGfGaXlchdPcRkxbkp6Uv7NODcxQFLEPEzQat/3V9gQU
|
||||||
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
|
0qMmytQcxQJBANpIWZd4XNVjD7D9jFJU+Y5TjhiYOq6ea35qWntdNDdVuSGOvUAy
|
||||||
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
|
DSg4fXifdvohi8wti2il9kGPu+ylF5qzr70CQFD+/DJklVlhbtZTThVFCTKdk6PY
|
||||||
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
|
ENvlvbmCKSz3i9i624Agro1X9LcdBThv/p6dsnHKNHejSZnbdvjl7OnA1J0CQBW3
|
||||||
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
|
TPJ8zv+Ls2vwTZ2DRrCaL3DS9EObDyasfgP36dH3fUuRX9KbKCPwOstdUgDghX/y
|
||||||
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
|
qAPpPu6W1iNc6VRCvCECQQCQp0XaiXCyzWSWYDJCKMX4KFb/1mW6moXI1g8bi+5x
|
||||||
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
|
fs0scurgHa2GunZU1M9FrbXx8rMdn4Eiz6XxpVcPmy0l
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
</PrivateKey>
|
</PrivateKey>
|
||||||
<CertificateChain>
|
<CertificateChain>
|
||||||
<NumberOfCertificates>2</NumberOfCertificates>
|
<NumberOfCertificates>2</NumberOfCertificates>
|
||||||
<Certificate format="pem">
|
<Certificate format="pem">
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
|
MIICtjCCAh+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMx
|
||||||
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT
|
||||||
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
|
BgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDAeFw0xNjAxMDQx
|
||||||
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
|
MjQwNTNaFw0zNTEyMzAxMjQwNTNaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
|
||||||
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
YWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||||
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
|
aWQxKTAnBgNVBAMMIEFuZHJvaWQgU29mdHdhcmUgQXR0ZXN0YXRpb24gS2V5MIGf
|
||||||
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
|
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAgyPcVogbuDAgafWwhWHG7r5/BeL1
|
||||||
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
|
qEIEir6LR752/q7yXPKbKvoyABQWAUKZiaFfz8aBXrNjWDwv0vIL5Jgyg92BSxbX
|
||||||
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
|
4YVBeuVKvClqOm21wAQIO2jFVsHwIzmRZBmGTVC3TUCuykhMdzVsiVoMJ1q/rEmd
|
||||||
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
|
XX0jYvKcXgLocQIDAQABo2YwZDAdBgNVHQ4EFgQU1AwQG/jNY7n3OVK1DhNcpteZ
|
||||||
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
|
k4YwHwYDVR0jBBgwFoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wEgYDVR0TAQH/BAgw
|
||||||
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
|
BgEB/wIBADAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAni1IX4xn
|
||||||
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
|
M9waha2Z11Aj6hTsQ7DhnerCI0YecrUZ3GAi5KVoMWwLVcTmnKItnzpPk2sxixZ4
|
||||||
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
|
Fg2Iy9mLzICdhPDCJ+NrOPH90ecXcjFZNX2W88V/q52PlmEmT7K+gbsNSQQiis6f
|
||||||
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
|
9/VCLiVE+iEHElqDtVWtGIL4QBSbnCBjBH8=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
</Certificate>
|
</Certificate>
|
||||||
<Certificate format="pem">
|
<Certificate format="pem">
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
|
MIICpzCCAhCgAwIBAgIJAP+U2d2fB8gMMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV
|
||||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||||
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
|
aWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQwHhcN
|
||||||
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
|
MTYwMTA0MTIzMTA4WhcNMzUxMjMwMTIzMTA4WjBjMQswCQYDVQQGEwJVUzETMBEG
|
||||||
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
|
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UE
|
||||||
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
|
CgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMIGfMA0GCSqGSIb3DQEB
|
||||||
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
|
AQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMfd5tjkWP/96r/C3JHTsAs
|
||||||
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
|
Q+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmgMdsGUmX4RFlXYfC78hdL
|
||||||
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
|
t0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm+Vfkl5YLCazOkjWFmwID
|
||||||
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
|
AQABo2MwYTAdBgNVHQ4EFgQUKfrxrMxN0kyWQCd1trDpMuUH/i4wHwYDVR0jBBgw
|
||||||
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
|
FoAUKfrxrMxN0kyWQCd1trDpMuUH/i4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
|
||||||
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
|
Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADgYEAT3LzNlmNDsG5dFsxWfbwjSVJMJ6j
|
||||||
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
|
HBwp0kUtILlNX2S06IDHeHqcOd6os/W/L3BfRxBcxebrTQaZYdKumgf/93y4q+uc
|
||||||
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
|
DyQHXrF/unlx/U1bnt8Uqf7f7XzAiF343ZtkMlbVNZriE/mPzsF83O+kqrJVw4Op
|
||||||
Lvtc9mL1J1IXvmM=
|
Lvtc9mL1J1IXvmM=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
</Certificate>
|
</Certificate>
|
||||||
</CertificateChain>
|
</CertificateChain>
|
||||||
</Key>
|
</Key>
|
||||||
</Keybox>
|
</Keybox>
|
||||||
</AndroidAttestation>
|
</AndroidAttestation>
|
||||||
|
|||||||
Reference in New Issue
Block a user