Add ktfmt-gradle as formatter
Run `gradle format` to format all kotlin source code
This commit is contained in:
+48
-40
@@ -3,26 +3,24 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.ktfmt)
|
||||
}
|
||||
|
||||
ktfmt { kotlinLangStyle() }
|
||||
|
||||
fun String.execute(currentWorkingDir: File = File("./")): String {
|
||||
val parts = this.split("\\s+".toRegex())
|
||||
val process = ProcessBuilder(parts)
|
||||
.directory(currentWorkingDir)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
val process =
|
||||
ProcessBuilder(parts).directory(currentWorkingDir).redirectErrorStream(true).start()
|
||||
|
||||
val output = process.inputStream.bufferedReader().readText()
|
||||
process.waitFor()
|
||||
return output.trim()
|
||||
}
|
||||
|
||||
|
||||
val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
|
||||
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
|
||||
val verName = "v2.1.0"
|
||||
@@ -61,16 +59,14 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
buildFeatures { prefab = true }
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -85,9 +81,7 @@ android {
|
||||
version = "3.28.0+"
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = false
|
||||
}
|
||||
buildFeatures { viewBinding = false }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -102,15 +96,15 @@ afterEvaluate {
|
||||
val variantName = variant.name
|
||||
val capitalized = variantName.replaceFirstChar { it.uppercase() }
|
||||
val tempModuleDir = project.layout.buildDirectory.dir("tmp/module-${variantName}")
|
||||
|
||||
|
||||
tasks.register("copyFiles${capitalized}") {
|
||||
dependsOn("assemble${capitalized}")
|
||||
val moduleFolder = project.rootDir.resolve("module")
|
||||
val buildDir = project.layout.buildDirectory
|
||||
|
||||
|
||||
doLast {
|
||||
val isDebug = variantName.contains("debug", ignoreCase = true)
|
||||
//val apkFile = variant.outputs.first().outputFile
|
||||
// val apkFile = variant.outputs.first().outputFile
|
||||
|
||||
listOf("service.apk", "classes.dex").forEach { fileName ->
|
||||
val oldFile = moduleFolder.resolve(fileName)
|
||||
@@ -118,23 +112,32 @@ afterEvaluate {
|
||||
}
|
||||
|
||||
// Select source file based on build type
|
||||
val sourceFile = if (isDebug) {
|
||||
variant.outputs.first().outputFile
|
||||
} else {
|
||||
buildDir.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex")
|
||||
}
|
||||
val sourceFile =
|
||||
if (isDebug) {
|
||||
variant.outputs.first().outputFile
|
||||
} else {
|
||||
buildDir
|
||||
.get()
|
||||
.asFile
|
||||
.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex")
|
||||
}
|
||||
|
||||
val destFileName = if (isDebug) "service.apk" else "classes.dex"
|
||||
sourceFile.copyTo(moduleFolder.resolve(destFileName), overwrite = true)
|
||||
|
||||
val soDir = buildDir.get()
|
||||
.asFile
|
||||
.resolve("intermediates/stripped_native_libs/$variantName/strip${capitalized}DebugSymbols/out/lib")
|
||||
|
||||
//apkFile.copyTo(moduleFolder.resolve("service.apk"), overwrite = true)
|
||||
|
||||
val soDir =
|
||||
buildDir
|
||||
.get()
|
||||
.asFile
|
||||
.resolve(
|
||||
"intermediates/stripped_native_libs/$variantName/strip${capitalized}DebugSymbols/out/lib"
|
||||
)
|
||||
|
||||
// apkFile.copyTo(moduleFolder.resolve("service.apk"), overwrite = true)
|
||||
|
||||
val allowedLibs = setOf("libinject.so", "libTrickyStoreOSS.so")
|
||||
soDir.walk()
|
||||
soDir
|
||||
.walk()
|
||||
.filter { it.isFile && it.name in allowedLibs }
|
||||
.forEach { soFile ->
|
||||
val abiFolder = soFile.parentFile.name
|
||||
@@ -143,21 +146,22 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Prepare temp directory with all files
|
||||
tasks.register("prepareModuleFiles${capitalized}") {
|
||||
dependsOn("copyFiles${capitalized}")
|
||||
val sourceDir = project.rootDir.resolve("module")
|
||||
|
||||
|
||||
doLast {
|
||||
val tempDir = tempModuleDir.get().asFile
|
||||
|
||||
|
||||
// Clean and create temp directory
|
||||
tempDir.deleteRecursively()
|
||||
tempDir.mkdirs()
|
||||
|
||||
|
||||
// Copy all files except module.prop
|
||||
sourceDir.walkTopDown()
|
||||
sourceDir
|
||||
.walkTopDown()
|
||||
.filter { it.isFile && it.name != "module.prop" }
|
||||
.forEach { sourceFile ->
|
||||
val relativePath = sourceFile.relativeTo(sourceDir)
|
||||
@@ -165,18 +169,22 @@ afterEvaluate {
|
||||
destFile.parentFile.mkdirs()
|
||||
sourceFile.copyTo(destFile, overwrite = true)
|
||||
}
|
||||
|
||||
|
||||
// Process module.prop
|
||||
val sourceProp = sourceDir.resolve("module.prop")
|
||||
val destProp = tempDir.resolve("module.prop")
|
||||
val content = sourceProp.readText()
|
||||
val processedContent = content
|
||||
.replace("REPLACEMEVERCODE", gitCommitCount.toString())
|
||||
.replace("REPLACEMEVER", "$verName ($gitCommitCount-$gitCommitHash-$variantName)")
|
||||
val processedContent =
|
||||
content
|
||||
.replace("REPLACEMEVERCODE", gitCommitCount.toString())
|
||||
.replace(
|
||||
"REPLACEMEVER",
|
||||
"$verName ($gitCommitCount-$gitCommitHash-$variantName)",
|
||||
)
|
||||
destProp.writeText(processedContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Zip task uses the temp directory
|
||||
val zipTask =
|
||||
tasks.register<Zip>("zip${capitalized}") {
|
||||
@@ -263,7 +271,7 @@ afterEvaluate {
|
||||
commandLine("adb", "reboot")
|
||||
description = "Installs the $variantName module via APatch and reboots."
|
||||
}
|
||||
|
||||
|
||||
tasks["assemble${capitalized}"].finalizedBy("zip${capitalized}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,42 +14,40 @@ import io.github.beakthoven.TrickyStoreOSS.AttestUtils.CachedAttestData
|
||||
import io.github.beakthoven.TrickyStoreOSS.config.CustomPatchLevel
|
||||
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
|
||||
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
import org.bouncycastle.asn1.ASN1Integer
|
||||
import org.bouncycastle.asn1.DEROctetString
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
|
||||
object AndroidUtils {
|
||||
|
||||
val bootKey: ByteArray by lazy {
|
||||
randomBytes()
|
||||
}
|
||||
val bootKey: ByteArray by lazy { randomBytes() }
|
||||
|
||||
fun setupBootHash() {
|
||||
getBootHashFromProp()?.also {
|
||||
getBootHashFromProp()?.also {
|
||||
Logger.d("Using boot hash from system property: ${it.toHex()}")
|
||||
}
|
||||
?: getBootHashFromAttestation()?.also {
|
||||
Logger.d("Using boot hash from attestation: ${it.toHex()}")
|
||||
setBootHashProp(it)
|
||||
}
|
||||
?: randomBytes().also {
|
||||
Logger.d("Generating random boot hash: ${it.toHex()}")
|
||||
setBootHashProp(it)
|
||||
}
|
||||
?: getBootHashFromAttestation()?.also {
|
||||
Logger.d("Using boot hash from attestation: ${it.toHex()}")
|
||||
setBootHashProp(it)
|
||||
}
|
||||
?: randomBytes().also {
|
||||
Logger.d("Generating random boot hash: ${it.toHex()}")
|
||||
setBootHashProp(it)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun getBootHashFromProp(): ByteArray? {
|
||||
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
|
||||
Logger.d("System property ro.boot.vbmeta.digest: $digest")
|
||||
|
||||
|
||||
if (digest.isBlank()) {
|
||||
Logger.d("Property is blank")
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
return if (digest.length == 64) digest.hexToByteArray() else null
|
||||
}
|
||||
|
||||
@@ -72,45 +70,51 @@ object AndroidUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private fun randomBytes(): ByteArray = ByteArray(32).also {
|
||||
ThreadLocalRandom.current().nextBytes(it)
|
||||
}
|
||||
private fun randomBytes(): ByteArray =
|
||||
ByteArray(32).also { ThreadLocalRandom.current().nextBytes(it) }
|
||||
|
||||
val patchLevel: Int
|
||||
get() = getCustomPatchLevel("system", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
get() =
|
||||
getCustomPatchLevel("system", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
|
||||
val patchLevelLong: Int
|
||||
get() = getCustomPatchLevel("system", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
get() =
|
||||
getCustomPatchLevel("system", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
|
||||
val vendorPatchLevel: Int
|
||||
get() = getCustomPatchLevel("vendor", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
get() =
|
||||
getCustomPatchLevel("vendor", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
|
||||
val vendorPatchLevelLong: Int
|
||||
get() = getCustomPatchLevel("vendor", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
get() =
|
||||
getCustomPatchLevel("vendor", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
|
||||
val bootPatchLevel: Int
|
||||
get() = getCustomPatchLevel("boot", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
get() =
|
||||
getCustomPatchLevel("boot", false)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
|
||||
|
||||
val bootPatchLevelLong: Int
|
||||
get() = getCustomPatchLevel("boot", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
get() =
|
||||
getCustomPatchLevel("boot", true)
|
||||
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
|
||||
|
||||
private val customPatchLevel: CustomPatchLevel?
|
||||
get() = PkgConfig._customPatchLevel
|
||||
|
||||
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
|
||||
val config = customPatchLevel ?: return null
|
||||
val value = when (component) {
|
||||
"system" -> config.system ?: config.all
|
||||
"vendor" -> config.vendor ?: config.all
|
||||
"boot" -> config.boot ?: config.all
|
||||
else -> config.all
|
||||
} ?: return null
|
||||
val value =
|
||||
when (component) {
|
||||
"system" -> config.system ?: config.all
|
||||
"vendor" -> config.vendor ?: config.all
|
||||
"boot" -> config.boot ?: config.all
|
||||
else -> config.all
|
||||
} ?: return null
|
||||
|
||||
when {
|
||||
value.equals("no", ignoreCase = true) -> return null
|
||||
@@ -122,21 +126,19 @@ object AndroidUtils {
|
||||
|
||||
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
|
||||
val normalized = value.replace("-", "")
|
||||
|
||||
|
||||
return try {
|
||||
when (normalized.length) {
|
||||
8 -> {
|
||||
val year = normalized.substring(0, 4).toInt()
|
||||
val month = normalized.substring(4, 6).toInt()
|
||||
val day = normalized.substring(6, 8).toInt()
|
||||
if (isLong) year * 10000 + month * 100 + day
|
||||
else year * 100 + month
|
||||
if (isLong) year * 10000 + month * 100 + day else year * 100 + month
|
||||
}
|
||||
6 -> {
|
||||
val year = normalized.substring(0, 4).toInt()
|
||||
val month = normalized.substring(4, 6).toInt()
|
||||
if (isLong) year * 10000 + month * 100
|
||||
else year * 100 + month
|
||||
if (isLong) year * 10000 + month * 100 else year * 100 + month
|
||||
}
|
||||
else -> {
|
||||
Logger.e("Invalid patch level length for $component: $normalized")
|
||||
@@ -149,30 +151,32 @@ object AndroidUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private val osVersionMap = mapOf(
|
||||
Build.VERSION_CODES.BAKLAVA to 160000,
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
|
||||
Build.VERSION_CODES.TIRAMISU to 130000,
|
||||
Build.VERSION_CODES.S_V2 to 120100,
|
||||
Build.VERSION_CODES.S to 120000,
|
||||
Build.VERSION_CODES.R to 110000,
|
||||
Build.VERSION_CODES.Q to 100000
|
||||
)
|
||||
private val osVersionMap =
|
||||
mapOf(
|
||||
Build.VERSION_CODES.BAKLAVA to 160000,
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
|
||||
Build.VERSION_CODES.TIRAMISU to 130000,
|
||||
Build.VERSION_CODES.S_V2 to 120100,
|
||||
Build.VERSION_CODES.S to 120000,
|
||||
Build.VERSION_CODES.R to 110000,
|
||||
Build.VERSION_CODES.Q to 100000,
|
||||
)
|
||||
|
||||
val osVersion: Int
|
||||
get() = CachedAttestData?.osVersion ?: osVersionMap[Build.VERSION.SDK_INT] ?: 160000
|
||||
|
||||
private val attestVersionMap = mapOf(
|
||||
Build.VERSION_CODES.Q to 4, // Keymaster 4.1
|
||||
Build.VERSION_CODES.R to 4, // Keymaster 4.1
|
||||
Build.VERSION_CODES.S to 100, // KeyMint 1.0
|
||||
Build.VERSION_CODES.S_V2 to 100, // KeyMint 1.0
|
||||
Build.VERSION_CODES.TIRAMISU to 200, // KeyMint 2.0
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 300, // KeyMint 3.0
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 300, // KeyMint 3.0
|
||||
Build.VERSION_CODES.BAKLAVA to 400 // KeyMint 4.0
|
||||
)
|
||||
private val attestVersionMap =
|
||||
mapOf(
|
||||
Build.VERSION_CODES.Q to 4, // Keymaster 4.1
|
||||
Build.VERSION_CODES.R to 4, // Keymaster 4.1
|
||||
Build.VERSION_CODES.S to 100, // KeyMint 1.0
|
||||
Build.VERSION_CODES.S_V2 to 100, // KeyMint 1.0
|
||||
Build.VERSION_CODES.TIRAMISU to 200, // KeyMint 2.0
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 300, // KeyMint 3.0
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 300, // KeyMint 3.0
|
||||
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
|
||||
)
|
||||
|
||||
val attestVersion: Int
|
||||
get() = CachedAttestData?.attestVersion ?: attestVersionMap[Build.VERSION.SDK_INT] ?: 400
|
||||
@@ -180,54 +184,56 @@ object AndroidUtils {
|
||||
val keymasterVersion: Int
|
||||
get() = CachedAttestData?.keymasterVersion ?: if (attestVersion == 4) 41 else attestVersion
|
||||
|
||||
fun String.convertPatchLevel(isLong: Boolean): Int = runCatching {
|
||||
val parts = split("-")
|
||||
when {
|
||||
isLong && parts.size >= 3 -> parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
|
||||
parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
|
||||
else -> throw IllegalArgumentException("Invalid patch level format: $this")
|
||||
}
|
||||
}.onFailure {
|
||||
Logger.e("Invalid patch level format: $this", it)
|
||||
}.getOrDefault(202404)
|
||||
fun String.convertPatchLevel(isLong: Boolean): Int =
|
||||
runCatching {
|
||||
val parts = split("-")
|
||||
when {
|
||||
isLong && parts.size >= 3 ->
|
||||
parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
|
||||
parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
|
||||
else -> throw IllegalArgumentException("Invalid patch level format: $this")
|
||||
}
|
||||
}
|
||||
.onFailure { Logger.e("Invalid patch level format: $this", it) }
|
||||
.getOrDefault(202404)
|
||||
|
||||
val apexInfos: List<Pair<String, Long>> by lazy {
|
||||
runCatching {
|
||||
val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||
val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
||||
val packageManager =
|
||||
IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
|
||||
val packages =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
||||
}
|
||||
|
||||
packages.list.map { it.packageName to it.longVersionCode }.sortedBy { it.first }
|
||||
}
|
||||
.getOrElse {
|
||||
Logger.e("Failed to get APEX package information")
|
||||
emptyList()
|
||||
}
|
||||
|
||||
packages.list
|
||||
.map { it.packageName to it.longVersionCode }
|
||||
.sortedBy { it.first }
|
||||
}.getOrElse {
|
||||
Logger.e("Failed to get APEX package information")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
val moduleHash: ByteArray by lazy {
|
||||
runCatching {
|
||||
val encodables = apexInfos.flatMap { (packageName, versionCode) ->
|
||||
listOf(
|
||||
DEROctetString(packageName.toByteArray()),
|
||||
ASN1Integer(versionCode)
|
||||
)
|
||||
}
|
||||
|
||||
val sequence = DERSequence(encodables.toTypedArray())
|
||||
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
||||
}.getOrElse {
|
||||
Logger.e("Failed to compute module hash", it)
|
||||
ByteArray(32)
|
||||
}
|
||||
}
|
||||
val encodables =
|
||||
apexInfos.flatMap { (packageName, versionCode) ->
|
||||
listOf(DEROctetString(packageName.toByteArray()), ASN1Integer(versionCode))
|
||||
}
|
||||
|
||||
val sequence = DERSequence(encodables.toTypedArray())
|
||||
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
||||
}
|
||||
.getOrElse {
|
||||
Logger.e("Failed to compute module hash", it)
|
||||
ByteArray(32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
|
||||
fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) }
|
||||
|
||||
fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) }
|
||||
|
||||
@@ -9,6 +9,11 @@ import android.os.Build
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import org.bouncycastle.asn1.ASN1Integer
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.ASN1OctetString
|
||||
@@ -16,11 +21,6 @@ import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.ASN1TaggedObject
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
|
||||
val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
|
||||
|
||||
@@ -33,7 +33,7 @@ object AttestUtils {
|
||||
)
|
||||
|
||||
val TEEStatus: Boolean by lazy { isTEEWorking() }
|
||||
val CachedAttestData: AttestationData? by lazy { getAttestData()}
|
||||
val CachedAttestData: AttestationData? by lazy { getAttestData() }
|
||||
|
||||
private val keygen_alias = "TrickyStoreOSS_attest"
|
||||
|
||||
@@ -52,22 +52,18 @@ object AttestUtils {
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||
keyStore.load(null)
|
||||
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
||||
val keyPairGenerator =
|
||||
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
||||
|
||||
val challenge = ByteArray(16).apply {
|
||||
SecureRandom().nextBytes(this)
|
||||
}
|
||||
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
|
||||
|
||||
val parameterSpec = KeyGenParameterSpec.Builder(
|
||||
keygen_alias,
|
||||
KeyProperties.PURPOSE_SIGN
|
||||
)
|
||||
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||
.setAttestationChallenge(challenge)
|
||||
.setIsStrongBoxBacked(false)
|
||||
.build()
|
||||
val parameterSpec =
|
||||
KeyGenParameterSpec.Builder(keygen_alias, KeyProperties.PURPOSE_SIGN)
|
||||
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||
.setAttestationChallenge(challenge)
|
||||
.setIsStrongBoxBacked(false)
|
||||
.build()
|
||||
|
||||
keyPairGenerator.initialize(parameterSpec)
|
||||
keyPairGenerator.generateKeyPair()
|
||||
@@ -86,7 +82,7 @@ object AttestUtils {
|
||||
return if (TEEStatus) {
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||
keyStore.load(null)
|
||||
|
||||
|
||||
val certChain = keyStore.getCertificateChain(keygen_alias)
|
||||
if (certChain == null || certChain.isEmpty()) {
|
||||
null
|
||||
@@ -104,10 +100,12 @@ object AttestUtils {
|
||||
|
||||
return try {
|
||||
val leafHolder = X509CertificateHolder(leaf.encoded)
|
||||
val ext: Extension = leafHolder.getExtension(ATTESTATION_OID) ?: run {
|
||||
Logger.i("No attestation extension found on certificate")
|
||||
return null
|
||||
}
|
||||
val ext: Extension =
|
||||
leafHolder.getExtension(ATTESTATION_OID)
|
||||
?: run {
|
||||
Logger.i("No attestation extension found on certificate")
|
||||
return null
|
||||
}
|
||||
|
||||
val keyDescriptionSeq = ASN1Sequence.getInstance(ext.extnValue.octets)
|
||||
val encodables = keyDescriptionSeq.toArray()
|
||||
@@ -123,13 +121,18 @@ object AttestUtils {
|
||||
val tagged = element as ASN1TaggedObject
|
||||
when (tagged.tagNo) {
|
||||
704 -> { // Parse Root of Trust
|
||||
val rootOfTrustSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
val rootOfTrustSeq =
|
||||
ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
if (rootOfTrustSeq.size() >= 4) {
|
||||
attestVerifiedBootHash = ASN1OctetString.getInstance(rootOfTrustSeq.getObjectAt(3)).octets
|
||||
attestVerifiedBootHash =
|
||||
ASN1OctetString.getInstance(rootOfTrustSeq.getObjectAt(3)).octets
|
||||
}
|
||||
}
|
||||
705 -> { // Parse OS Version
|
||||
attestOSVersion = ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive()).value.intValueExact()
|
||||
attestOSVersion =
|
||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
.value
|
||||
.intValueExact()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,11 +146,11 @@ object AttestUtils {
|
||||
verifiedBootHash = attestVerifiedBootHash,
|
||||
attestVersion = attestVersion,
|
||||
keymasterVersion = keymasterVersion,
|
||||
osVersion = attestOSVersion
|
||||
osVersion = attestOSVersion,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Failed to parse attestation data", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,18 @@ import android.util.Pair
|
||||
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
|
||||
import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor
|
||||
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
import java.math.BigInteger
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.MessageDigest
|
||||
import java.security.Security
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.RSAKeyGenParameterSpec
|
||||
import java.util.Date
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import org.bouncycastle.asn1.ASN1Boolean
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.ASN1Enumerated
|
||||
@@ -36,24 +48,12 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.openssl.PEMKeyPair
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import java.math.BigInteger
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.MessageDigest
|
||||
import java.security.Security
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.RSAKeyGenParameterSpec
|
||||
import java.util.Date
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
object CertificateGen {
|
||||
data class KeyBox(
|
||||
val pemKeyPair: PEMKeyPair,
|
||||
val keyPair: KeyPair,
|
||||
val certificates: List<Certificate>
|
||||
val certificates: List<Certificate>,
|
||||
)
|
||||
|
||||
private data class Digest(val digest: ByteArray) {
|
||||
@@ -63,7 +63,7 @@ object CertificateGen {
|
||||
other as Digest
|
||||
return digest.contentEquals(other.digest)
|
||||
}
|
||||
|
||||
|
||||
override fun hashCode(): Int = digest.contentHashCode()
|
||||
}
|
||||
|
||||
@@ -88,21 +88,22 @@ object CertificateGen {
|
||||
var imei1: ByteArray? = null,
|
||||
var imei2: ByteArray? = null,
|
||||
var meid: ByteArray? = null,
|
||||
var serialno: ByteArray? = null
|
||||
var serialno: ByteArray? = null,
|
||||
) {
|
||||
|
||||
|
||||
constructor(params: Array<KeyParameter>) : this() {
|
||||
params.forEach { param ->
|
||||
Logger.d("Processing key parameter: ${param.tag}")
|
||||
val value = param.value
|
||||
|
||||
|
||||
when (param.tag) {
|
||||
Tag.KEY_SIZE -> keySize = value.integer
|
||||
Tag.ALGORITHM -> algorithm = value.algorithm
|
||||
Tag.CERTIFICATE_SERIAL -> certificateSerial = BigInteger(value.blob)
|
||||
Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = Date(value.dateTime)
|
||||
Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = Date(value.dateTime)
|
||||
Tag.CERTIFICATE_SUBJECT -> certificateSubject = X500Name(X500Principal(value.blob).name)
|
||||
Tag.CERTIFICATE_SUBJECT ->
|
||||
certificateSubject = X500Name(X500Principal(value.blob).name)
|
||||
Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
|
||||
Tag.EC_CURVE -> {
|
||||
ecCurve = value.ecCurve
|
||||
@@ -126,146 +127,181 @@ object CertificateGen {
|
||||
ecCurveName = ecCurveMapKeySize(keySize)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ecCurveMapKeySize(curveSize: Int): String = when (curveSize) {
|
||||
224 -> "secp224r1"
|
||||
256 -> "secp256r1"
|
||||
384 -> "secp384r1"
|
||||
521 -> "secp521r1"
|
||||
else -> "secp256r1" // default fallback
|
||||
}
|
||||
|
||||
|
||||
private fun getEcCurveName(curve: Int): String = when (curve) {
|
||||
EcCurve.CURVE_25519 -> "CURVE_25519"
|
||||
EcCurve.P_224 -> "secp224r1"
|
||||
EcCurve.P_256 -> "secp256r1"
|
||||
EcCurve.P_384 -> "secp384r1"
|
||||
EcCurve.P_521 -> "secp521r1"
|
||||
else -> throw IllegalArgumentException("Unknown EC curve: $curve")
|
||||
}
|
||||
|
||||
private fun ecCurveMapKeySize(curveSize: Int): String =
|
||||
when (curveSize) {
|
||||
224 -> "secp224r1"
|
||||
256 -> "secp256r1"
|
||||
384 -> "secp384r1"
|
||||
521 -> "secp521r1"
|
||||
else -> "secp256r1" // default fallback
|
||||
}
|
||||
|
||||
private fun getEcCurveName(curve: Int): String =
|
||||
when (curve) {
|
||||
EcCurve.CURVE_25519 -> "CURVE_25519"
|
||||
EcCurve.P_224 -> "secp224r1"
|
||||
EcCurve.P_256 -> "secp256r1"
|
||||
EcCurve.P_384 -> "secp384r1"
|
||||
EcCurve.P_521 -> "secp521r1"
|
||||
else -> throw IllegalArgumentException("Unknown EC curve: $curve")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
|
||||
other as KeyGenParameters
|
||||
|
||||
|
||||
return keySize == other.keySize &&
|
||||
algorithm == other.algorithm &&
|
||||
certificateSerial == other.certificateSerial &&
|
||||
certificateNotBefore == other.certificateNotBefore &&
|
||||
certificateNotAfter == other.certificateNotAfter &&
|
||||
certificateSubject == other.certificateSubject &&
|
||||
rsaPublicExponent == other.rsaPublicExponent &&
|
||||
ecCurve == other.ecCurve &&
|
||||
ecCurveName == other.ecCurveName &&
|
||||
purpose == other.purpose &&
|
||||
digest == other.digest &&
|
||||
attestationChallenge.contentEquals(other.attestationChallenge) &&
|
||||
brand.contentEquals(other.brand) &&
|
||||
device.contentEquals(other.device) &&
|
||||
product.contentEquals(other.product) &&
|
||||
manufacturer.contentEquals(other.manufacturer) &&
|
||||
model.contentEquals(other.model) &&
|
||||
imei1.contentEquals(other.imei1) &&
|
||||
imei2.contentEquals(other.imei2) &&
|
||||
meid.contentEquals(other.meid) &&
|
||||
serialno.contentEquals(other.serialno)
|
||||
algorithm == other.algorithm &&
|
||||
certificateSerial == other.certificateSerial &&
|
||||
certificateNotBefore == other.certificateNotBefore &&
|
||||
certificateNotAfter == other.certificateNotAfter &&
|
||||
certificateSubject == other.certificateSubject &&
|
||||
rsaPublicExponent == other.rsaPublicExponent &&
|
||||
ecCurve == other.ecCurve &&
|
||||
ecCurveName == other.ecCurveName &&
|
||||
purpose == other.purpose &&
|
||||
digest == other.digest &&
|
||||
attestationChallenge.contentEquals(other.attestationChallenge) &&
|
||||
brand.contentEquals(other.brand) &&
|
||||
device.contentEquals(other.device) &&
|
||||
product.contentEquals(other.product) &&
|
||||
manufacturer.contentEquals(other.manufacturer) &&
|
||||
model.contentEquals(other.model) &&
|
||||
imei1.contentEquals(other.imei1) &&
|
||||
imei2.contentEquals(other.imei2) &&
|
||||
meid.contentEquals(other.meid) &&
|
||||
serialno.contentEquals(other.serialno)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateChain(uid: Int, params: KeyGenParameters, keyPair: KeyPair, securityLevel: Int = 1): List<ByteArray>? = runCatching {
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) ?: return null
|
||||
|
||||
val issuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, securityLevel)
|
||||
|
||||
val chain = buildList {
|
||||
add(leaf)
|
||||
addAll(keybox.certificates)
|
||||
}
|
||||
|
||||
CertificateUtils.run { chain.toByteArrayList() }
|
||||
}.onFailure {
|
||||
Logger.e("Failed to generate certificate chain", it)
|
||||
}.getOrNull()
|
||||
fun generateChain(
|
||||
uid: Int,
|
||||
params: KeyGenParameters,
|
||||
keyPair: KeyPair,
|
||||
securityLevel: Int = 1,
|
||||
): List<ByteArray>? =
|
||||
runCatching {
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) ?: return null
|
||||
|
||||
fun generateKeyPair(params: KeyGenParameters): KeyPair? = runCatching {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
val issuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, securityLevel)
|
||||
|
||||
val (keyPairGenerator, spec) = when (params.algorithm) {
|
||||
Algorithm.EC -> {
|
||||
Logger.d("Generating EC keypair of size ${params.keySize}")
|
||||
val spec = ECGenParameterSpec(params.ecCurveName)
|
||||
val kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME)
|
||||
kpg to spec
|
||||
val chain = buildList {
|
||||
add(leaf)
|
||||
addAll(keybox.certificates)
|
||||
}
|
||||
|
||||
CertificateUtils.run { chain.toByteArrayList() }
|
||||
}
|
||||
Algorithm.RSA -> {
|
||||
Logger.d("Generating RSA keypair of size ${params.keySize}")
|
||||
val spec = RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
|
||||
val kpg = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
|
||||
kpg to spec
|
||||
.onFailure { Logger.e("Failed to generate certificate chain", it) }
|
||||
.getOrNull()
|
||||
|
||||
fun generateKeyPair(params: KeyGenParameters): KeyPair? =
|
||||
runCatching {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
|
||||
val (keyPairGenerator, spec) =
|
||||
when (params.algorithm) {
|
||||
Algorithm.EC -> {
|
||||
Logger.d("Generating EC keypair of size ${params.keySize}")
|
||||
val spec = ECGenParameterSpec(params.ecCurveName)
|
||||
val kpg =
|
||||
KeyPairGenerator.getInstance(
|
||||
"EC",
|
||||
BouncyCastleProvider.PROVIDER_NAME,
|
||||
)
|
||||
kpg to spec
|
||||
}
|
||||
Algorithm.RSA -> {
|
||||
Logger.d("Generating RSA keypair of size ${params.keySize}")
|
||||
val spec =
|
||||
RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
|
||||
val kpg =
|
||||
KeyPairGenerator.getInstance(
|
||||
"RSA",
|
||||
BouncyCastleProvider.PROVIDER_NAME,
|
||||
)
|
||||
kpg to spec
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException(
|
||||
"Unsupported algorithm: ${params.algorithm}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
keyPairGenerator.initialize(spec)
|
||||
keyPairGenerator.generateKeyPair()
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||
}
|
||||
}
|
||||
.onFailure { Logger.e("Failed to generate key pair", it) }
|
||||
.getOrNull()
|
||||
|
||||
keyPairGenerator.initialize(spec)
|
||||
keyPairGenerator.generateKeyPair()
|
||||
}.onFailure {
|
||||
Logger.e("Failed to generate key pair", it)
|
||||
}.getOrNull()
|
||||
|
||||
fun generateKeyPair(
|
||||
uid: Int,
|
||||
descriptor: KeyDescriptor,
|
||||
attestKeyDescriptor: KeyDescriptor?,
|
||||
params: KeyGenParameters,
|
||||
securityLevel: Int = 1
|
||||
): Pair<KeyPair, List<Certificate>>? = runCatching {
|
||||
Logger.i("Requested KeyPair with alias: ${descriptor.alias}")
|
||||
|
||||
val hasAttestKey = attestKeyDescriptor != null
|
||||
if (hasAttestKey) {
|
||||
Logger.i("Requested KeyPair with attestKey: ${attestKeyDescriptor?.alias}")
|
||||
}
|
||||
|
||||
val keyPair = generateKeyPair(params) ?: return null
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) ?: return null
|
||||
|
||||
val (signingKeyPair, issuer) = if (hasAttestKey) {
|
||||
getAttestationKeyInfo(uid, attestKeyDescriptor!!)?.let {
|
||||
it.first to it.second
|
||||
} ?: (keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject)
|
||||
} else {
|
||||
keybox.keyPair to X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
}
|
||||
|
||||
val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, securityLevel, signingKeyPair)
|
||||
val chain = buildList {
|
||||
add(leaf)
|
||||
if (!hasAttestKey) {
|
||||
addAll(keybox.certificates)
|
||||
securityLevel: Int = 1,
|
||||
): Pair<KeyPair, List<Certificate>>? =
|
||||
runCatching {
|
||||
Logger.i("Requested KeyPair with alias: ${descriptor.alias}")
|
||||
|
||||
val hasAttestKey = attestKeyDescriptor != null
|
||||
if (hasAttestKey) {
|
||||
Logger.i("Requested KeyPair with attestKey: ${attestKeyDescriptor?.alias}")
|
||||
}
|
||||
|
||||
val keyPair = generateKeyPair(params) ?: return null
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) ?: return null
|
||||
|
||||
val (signingKeyPair, issuer) =
|
||||
if (hasAttestKey) {
|
||||
getAttestationKeyInfo(uid, attestKeyDescriptor!!)?.let {
|
||||
it.first to it.second
|
||||
}
|
||||
?: (keybox.keyPair to
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject)
|
||||
} else {
|
||||
keybox.keyPair to
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
}
|
||||
|
||||
val leaf =
|
||||
buildCertificate(
|
||||
keyPair,
|
||||
keybox,
|
||||
params,
|
||||
issuer,
|
||||
uid,
|
||||
securityLevel,
|
||||
signingKeyPair,
|
||||
)
|
||||
val chain = buildList {
|
||||
add(leaf)
|
||||
if (!hasAttestKey) {
|
||||
addAll(keybox.certificates)
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("Successfully generated certificate for alias: ${descriptor.alias}")
|
||||
Pair(keyPair, chain)
|
||||
}
|
||||
.onFailure { Logger.e("Failed to generate key pair with certificates", it) }
|
||||
.getOrNull()
|
||||
|
||||
private fun mapAlgorithmToName(algorithm: Int): String? =
|
||||
when (algorithm) {
|
||||
Algorithm.EC -> KeyProperties.KEY_ALGORITHM_EC
|
||||
Algorithm.RSA -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
else -> {
|
||||
Logger.e("Unsupported algorithm: $algorithm")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("Successfully generated certificate for alias: ${descriptor.alias}")
|
||||
Pair(keyPair, chain)
|
||||
}.onFailure {
|
||||
Logger.e("Failed to generate key pair with certificates", it)
|
||||
}.getOrNull()
|
||||
|
||||
private fun mapAlgorithmToName(algorithm: Int): String? = when (algorithm) {
|
||||
Algorithm.EC -> KeyProperties.KEY_ALGORITHM_EC
|
||||
Algorithm.RSA -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
else -> {
|
||||
Logger.e("Unsupported algorithm: $algorithm")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox? {
|
||||
val algorithmName = mapAlgorithmToName(algorithm) ?: return null
|
||||
@@ -273,9 +309,12 @@ object CertificateGen {
|
||||
return KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
|
||||
}
|
||||
|
||||
private fun getAttestationKeyInfo(uid: Int, attestKeyDescriptor: KeyDescriptor): Pair<KeyPair, X500Name>? {
|
||||
private fun getAttestationKeyInfo(
|
||||
uid: Int,
|
||||
attestKeyDescriptor: KeyDescriptor,
|
||||
): Pair<KeyPair, X500Name>? {
|
||||
Logger.d("Looking for attestation key: uid=$uid alias=${attestKeyDescriptor.alias}")
|
||||
|
||||
|
||||
val keyInfo = SecurityLevelInterceptor.getKeyPairs(uid, attestKeyDescriptor.alias)
|
||||
return if (keyInfo != null) {
|
||||
val issuer = X509CertificateHolder(keyInfo.second[0].encoded).subject
|
||||
@@ -293,52 +332,59 @@ object CertificateGen {
|
||||
issuer: X500Name,
|
||||
uid: Int,
|
||||
securityLevel: Int = 1,
|
||||
signingKeyPair: KeyPair = keybox.keyPair
|
||||
signingKeyPair: KeyPair = keybox.keyPair,
|
||||
): Certificate {
|
||||
val builder = JcaX509v3CertificateBuilder(
|
||||
issuer,
|
||||
params.certificateSerial ?: BigInteger.ONE,
|
||||
params.certificateNotBefore ?: Date(),
|
||||
params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
|
||||
params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
|
||||
keyPair.public
|
||||
)
|
||||
|
||||
val builder =
|
||||
JcaX509v3CertificateBuilder(
|
||||
issuer,
|
||||
params.certificateSerial ?: BigInteger.ONE,
|
||||
params.certificateNotBefore ?: Date(),
|
||||
params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
|
||||
params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
|
||||
keyPair.public,
|
||||
)
|
||||
|
||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
||||
builder.addExtension(buildAttestExtension(params, uid, securityLevel))
|
||||
|
||||
val signerAlgorithm = when (params.algorithm) {
|
||||
Algorithm.EC -> "SHA256withECDSA"
|
||||
Algorithm.RSA -> "SHA256withRSA"
|
||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||
}
|
||||
|
||||
val signerAlgorithm =
|
||||
when (params.algorithm) {
|
||||
Algorithm.EC -> "SHA256withECDSA"
|
||||
Algorithm.RSA -> "SHA256withRSA"
|
||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||
}
|
||||
val contentSigner = JcaContentSignerBuilder(signerAlgorithm).build(signingKeyPair.private)
|
||||
|
||||
|
||||
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
||||
}
|
||||
|
||||
private fun buildAttestExtension(params: KeyGenParameters, uid: Int, securityLevel: Int = 1): Extension {
|
||||
private fun buildAttestExtension(
|
||||
params: KeyGenParameters,
|
||||
uid: Int,
|
||||
securityLevel: Int = 1,
|
||||
): Extension {
|
||||
try {
|
||||
val key = AndroidUtils.bootKey
|
||||
val hash = AndroidUtils.getBootHashFromProp()
|
||||
|
||||
Logger.d("Using boothash ${hash?.toHex() ?: 0}")
|
||||
|
||||
val rootOfTrustEncodables = arrayOf(
|
||||
DEROctetString(key),
|
||||
ASN1Boolean.TRUE,
|
||||
ASN1Enumerated(0),
|
||||
DEROctetString(hash)
|
||||
)
|
||||
|
||||
val rootOfTrustEncodables =
|
||||
arrayOf(
|
||||
DEROctetString(key),
|
||||
ASN1Boolean.TRUE,
|
||||
ASN1Enumerated(0),
|
||||
DEROctetString(hash),
|
||||
)
|
||||
val rootOfTrustSeq = DERSequence(rootOfTrustEncodables)
|
||||
|
||||
|
||||
val purpose = DERSet(params.purpose.map { ASN1Integer(it.toLong()) }.toTypedArray())
|
||||
val algorithm = ASN1Integer(params.algorithm.toLong())
|
||||
val keySize = ASN1Integer(params.keySize.toLong())
|
||||
val digest = DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray())
|
||||
val ecCurve = ASN1Integer(params.ecCurve.toLong())
|
||||
val noAuthRequired = DERNull.INSTANCE
|
||||
|
||||
|
||||
val osVersion = ASN1Integer(AndroidUtils.osVersion.toLong())
|
||||
val osPatchLevel = ASN1Integer(AndroidUtils.patchLevel.toLong())
|
||||
val applicationID = createApplicationId(uid)
|
||||
@@ -347,51 +393,76 @@ object CertificateGen {
|
||||
val creationDateTime = ASN1Integer(System.currentTimeMillis())
|
||||
val origin = ASN1Integer(0L)
|
||||
val moduleHash = DEROctetString(AndroidUtils.moduleHash)
|
||||
|
||||
val teeEnforcedObjects = mutableListOf(
|
||||
DERTaggedObject(true, 1, purpose),
|
||||
DERTaggedObject(true, 2, algorithm),
|
||||
DERTaggedObject(true, 3, keySize),
|
||||
DERTaggedObject(true, 5, digest),
|
||||
DERTaggedObject(true, 10, ecCurve),
|
||||
DERTaggedObject(true, 503, noAuthRequired),
|
||||
DERTaggedObject(true, 702, origin),
|
||||
DERTaggedObject(true, 704, rootOfTrustSeq),
|
||||
DERTaggedObject(true, 705, osVersion),
|
||||
DERTaggedObject(true, 706, osPatchLevel),
|
||||
DERTaggedObject(true, 718, vendorPatchLevel),
|
||||
DERTaggedObject(true, 719, bootPatchLevel),
|
||||
)
|
||||
|
||||
val teeEnforcedObjects =
|
||||
mutableListOf(
|
||||
DERTaggedObject(true, 1, purpose),
|
||||
DERTaggedObject(true, 2, algorithm),
|
||||
DERTaggedObject(true, 3, keySize),
|
||||
DERTaggedObject(true, 5, digest),
|
||||
DERTaggedObject(true, 10, ecCurve),
|
||||
DERTaggedObject(true, 503, noAuthRequired),
|
||||
DERTaggedObject(true, 702, origin),
|
||||
DERTaggedObject(true, 704, rootOfTrustSeq),
|
||||
DERTaggedObject(true, 705, osVersion),
|
||||
DERTaggedObject(true, 706, osPatchLevel),
|
||||
DERTaggedObject(true, 718, vendorPatchLevel),
|
||||
DERTaggedObject(true, 719, bootPatchLevel),
|
||||
)
|
||||
|
||||
if (AndroidUtils.attestVersion >= 400) {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 724, moduleHash))
|
||||
}
|
||||
|
||||
params.brand?.let { teeEnforcedObjects.add(DERTaggedObject(true, 710, DEROctetString(it))) }
|
||||
params.device?.let { teeEnforcedObjects.add(DERTaggedObject(true, 711, DEROctetString(it))) }
|
||||
params.product?.let { teeEnforcedObjects.add(DERTaggedObject(true, 712, DEROctetString(it))) }
|
||||
params.manufacturer?.let { teeEnforcedObjects.add(DERTaggedObject(true, 716, DEROctetString(it))) }
|
||||
params.model?.let { teeEnforcedObjects.add(DERTaggedObject(true, 717, DEROctetString(it))) }
|
||||
|
||||
params.serialno?.let { teeEnforcedObjects.add(DERTaggedObject(true, 713, DEROctetString(it))) }
|
||||
params.imei1?.let { teeEnforcedObjects.add(DERTaggedObject(true, 714, DEROctetString(it))) }
|
||||
params.meid?.let { teeEnforcedObjects.add(DERTaggedObject(true, 715, DEROctetString(it))) }
|
||||
|
||||
params.brand?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 710, DEROctetString(it)))
|
||||
}
|
||||
params.device?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 711, DEROctetString(it)))
|
||||
}
|
||||
params.product?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 712, DEROctetString(it)))
|
||||
}
|
||||
params.manufacturer?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 716, DEROctetString(it)))
|
||||
}
|
||||
params.model?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 717, DEROctetString(it)))
|
||||
}
|
||||
|
||||
params.serialno?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 713, DEROctetString(it)))
|
||||
}
|
||||
params.imei1?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 714, DEROctetString(it)))
|
||||
}
|
||||
params.meid?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 715, DEROctetString(it)))
|
||||
}
|
||||
|
||||
if (AndroidUtils.attestVersion >= 300) {
|
||||
params.imei2?.let { teeEnforcedObjects.add(DERTaggedObject(true, 723, DEROctetString(it))) }
|
||||
params.imei2?.let {
|
||||
teeEnforcedObjects.add(DERTaggedObject(true, 723, DEROctetString(it)))
|
||||
}
|
||||
}
|
||||
|
||||
teeEnforcedObjects.sortBy { it.tagNo }
|
||||
|
||||
val softwareEnforcedObjects = arrayOf<ASN1Encodable>(
|
||||
DERTaggedObject(true, 709, applicationID),
|
||||
DERTaggedObject(true, 701, creationDateTime)
|
||||
)
|
||||
|
||||
|
||||
val softwareEnforcedObjects =
|
||||
arrayOf<ASN1Encodable>(
|
||||
DERTaggedObject(true, 709, applicationID),
|
||||
DERTaggedObject(true, 701, creationDateTime),
|
||||
)
|
||||
|
||||
return Extension(
|
||||
ATTESTATION_OID,
|
||||
false,
|
||||
buildKeyDescriptionOctet(teeEnforcedObjects.toTypedArray(), softwareEnforcedObjects, params, securityLevel)
|
||||
buildKeyDescriptionOctet(
|
||||
teeEnforcedObjects.toTypedArray(),
|
||||
softwareEnforcedObjects,
|
||||
params,
|
||||
securityLevel,
|
||||
),
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to create attestation extension", t)
|
||||
@@ -403,7 +474,7 @@ object CertificateGen {
|
||||
teeEnforcedEncodables: Array<ASN1Encodable>,
|
||||
softwareEnforcedEncodables: Array<ASN1Encodable>,
|
||||
params: KeyGenParameters,
|
||||
securityLevel: Int = 1
|
||||
securityLevel: Int = 1,
|
||||
): ASN1OctetString {
|
||||
val attestationVersion = ASN1Integer(AndroidUtils.attestVersion.toLong())
|
||||
val attestationSecurityLevel = ASN1Enumerated(securityLevel)
|
||||
@@ -413,18 +484,19 @@ object CertificateGen {
|
||||
val uniqueId = DEROctetString(ByteArray(0))
|
||||
val softwareEnforced = DERSequence(softwareEnforcedEncodables)
|
||||
val teeEnforced = DERSequence(teeEnforcedEncodables)
|
||||
|
||||
val keyDescriptionEncodables = arrayOf(
|
||||
attestationVersion,
|
||||
attestationSecurityLevel,
|
||||
keymasterVersion,
|
||||
keymasterSecurityLevel,
|
||||
attestationChallenge,
|
||||
uniqueId,
|
||||
softwareEnforced,
|
||||
teeEnforced
|
||||
)
|
||||
|
||||
|
||||
val keyDescriptionEncodables =
|
||||
arrayOf(
|
||||
attestationVersion,
|
||||
attestationSecurityLevel,
|
||||
keymasterVersion,
|
||||
keymasterSecurityLevel,
|
||||
attestationChallenge,
|
||||
uniqueId,
|
||||
softwareEnforced,
|
||||
teeEnforced,
|
||||
)
|
||||
|
||||
val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
|
||||
return DEROctetString(keyDescriptionSeq.encoded)
|
||||
}
|
||||
@@ -432,40 +504,51 @@ object CertificateGen {
|
||||
@Throws(Throwable::class)
|
||||
private fun createApplicationId(uid: Int): DEROctetString {
|
||||
val pm = PkgConfig.getPm() ?: throw IllegalStateException("PackageManager not found!")
|
||||
val packages = pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||
val packages =
|
||||
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||
|
||||
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||
val signatures = mutableSetOf<Digest>()
|
||||
|
||||
val packageInfos = packages.map { packageName ->
|
||||
val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES.toLong(), uid / 100000)
|
||||
} else {
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, uid / 100000)
|
||||
val packageInfos =
|
||||
packages.map { packageName ->
|
||||
val info =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
pm.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
|
||||
uid / 100000,
|
||||
)
|
||||
} else {
|
||||
pm.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES,
|
||||
uid / 100000,
|
||||
)
|
||||
}
|
||||
|
||||
info.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
||||
signatures.add(Digest(messageDigest.digest(signature.toByteArray())))
|
||||
}
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
info.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
||||
signatures.add(Digest(messageDigest.digest(signature.toByteArray())))
|
||||
}
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
val packageInfoArray = packageInfos.map { info ->
|
||||
DERSequence(
|
||||
arrayOf(
|
||||
DEROctetString(info.packageName.toByteArray(StandardCharsets.UTF_8)),
|
||||
ASN1Integer(info.longVersionCode)
|
||||
)
|
||||
)
|
||||
}.toTypedArray()
|
||||
val packageInfoArray =
|
||||
packageInfos
|
||||
.map { info ->
|
||||
DERSequence(
|
||||
arrayOf(
|
||||
DEROctetString(info.packageName.toByteArray(StandardCharsets.UTF_8)),
|
||||
ASN1Integer(info.longVersionCode),
|
||||
)
|
||||
)
|
||||
}
|
||||
.toTypedArray()
|
||||
|
||||
val signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
|
||||
|
||||
val applicationIdArray = arrayOf(
|
||||
DERSet(packageInfoArray),
|
||||
DERSet(signaturesArray)
|
||||
)
|
||||
val applicationIdArray = arrayOf(DERSet(packageInfoArray), DERSet(signaturesArray))
|
||||
|
||||
return DEROctetString(DERSequence(applicationIdArray).encoded)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@ package io.github.beakthoven.TrickyStoreOSS
|
||||
|
||||
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
|
||||
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.LinkedList
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.bouncycastle.asn1.ASN1Boolean
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||
@@ -22,14 +28,8 @@ import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.LinkedList
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object CertificateHack {
|
||||
object CertificateHack {
|
||||
private val certificateFactory: CertificateFactory by lazy {
|
||||
try {
|
||||
CertificateFactory.getInstance("X.509")
|
||||
@@ -38,40 +38,39 @@ object CertificateHack {
|
||||
throw RuntimeException("Cannot initialize certificate factory", t)
|
||||
}
|
||||
}
|
||||
|
||||
data class KeyIdentifier(
|
||||
val alias: String,
|
||||
val uid: Int
|
||||
)
|
||||
|
||||
data class KeyIdentifier(val alias: String, val uid: Int)
|
||||
|
||||
val leafAlgorithms = ConcurrentHashMap<KeyIdentifier, String>()
|
||||
|
||||
|
||||
fun clearLeafAlgorithms() {
|
||||
leafAlgorithms.clear()
|
||||
}
|
||||
|
||||
|
||||
fun hackCertificateChain(certificateChain: Array<Certificate>?, uid: Int): Array<Certificate> {
|
||||
if (certificateChain == null) {
|
||||
throw UnsupportedOperationException("Certificate chain is null!")
|
||||
}
|
||||
|
||||
|
||||
return try {
|
||||
val leaf = certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(certificateChain[0].encoded)
|
||||
) as X509Certificate
|
||||
|
||||
val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
|
||||
?: return certificateChain // No attestation extension, return original
|
||||
val leaf =
|
||||
certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(certificateChain[0].encoded)
|
||||
) as X509Certificate
|
||||
|
||||
val extensionBytes =
|
||||
leaf.getExtensionValue(ATTESTATION_OID.id)
|
||||
?: return certificateChain // No attestation extension, return original
|
||||
|
||||
val leafHolder = X509CertificateHolder(leaf.encoded)
|
||||
val extension = leafHolder.getExtension(ATTESTATION_OID)
|
||||
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||
val encodables = sequence.toArray()
|
||||
val teeEnforced = encodables[7] as ASN1Sequence
|
||||
|
||||
|
||||
val vector = ASN1EncodableVector()
|
||||
var rootOfTrust: ASN1Encodable? = null
|
||||
|
||||
|
||||
teeEnforced.forEach { element ->
|
||||
val taggedObject = element as ASN1TaggedObject
|
||||
if (taggedObject.tagNo == 704) {
|
||||
@@ -80,86 +79,98 @@ object CertificateHack {
|
||||
vector.add(taggedObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
|
||||
val algorithmName = leaf.publicKey.algorithm
|
||||
val keybox = KeyBoxUtils.getKeybox(keyboxFileName, algorithmName) ?: throw UnsupportedOperationException("Unsupported algorithm '$algorithmName' in keybox '$keyboxFileName'")
|
||||
|
||||
val keybox =
|
||||
KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
|
||||
?: throw UnsupportedOperationException(
|
||||
"Unsupported algorithm '$algorithmName' in keybox '$keyboxFileName'"
|
||||
)
|
||||
|
||||
val certificates = LinkedList(keybox.certificates)
|
||||
val builder = X509v3CertificateBuilder(
|
||||
X509CertificateHolder(certificates[0].encoded).subject,
|
||||
leafHolder.serialNumber,
|
||||
leafHolder.notBefore,
|
||||
leafHolder.notAfter,
|
||||
leafHolder.subject,
|
||||
leafHolder.subjectPublicKeyInfo
|
||||
)
|
||||
|
||||
val builder =
|
||||
X509v3CertificateBuilder(
|
||||
X509CertificateHolder(certificates[0].encoded).subject,
|
||||
leafHolder.serialNumber,
|
||||
leafHolder.notBefore,
|
||||
leafHolder.notAfter,
|
||||
leafHolder.subject,
|
||||
leafHolder.subjectPublicKeyInfo,
|
||||
)
|
||||
|
||||
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
|
||||
|
||||
|
||||
val hackedExtension = hackAttestExtension(rootOfTrust, vector, encodables)
|
||||
builder.addExtension(hackedExtension)
|
||||
|
||||
|
||||
leafHolder.extensions.extensionOIDs.forEach { oid ->
|
||||
if (oid.id != ATTESTATION_OID.id) {
|
||||
builder.addExtension(leafHolder.getExtension(oid))
|
||||
}
|
||||
}
|
||||
|
||||
certificates.addFirst(JcaX509CertificateConverter().getCertificate(builder.build(signer)))
|
||||
|
||||
certificates.addFirst(
|
||||
JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||
)
|
||||
certificates.toTypedArray()
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to hack certificate chain for uid=$uid", t)
|
||||
certificateChain
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun hackCACertificateChain(caList: ByteArray?, alias: String, uid: Int): ByteArray {
|
||||
if (caList == null) {
|
||||
throw UnsupportedOperationException("CA list is null!")
|
||||
}
|
||||
|
||||
|
||||
return try {
|
||||
val key = KeyIdentifier(alias, uid)
|
||||
val algorithm = leafAlgorithms.remove(key)
|
||||
?: throw UnsupportedOperationException("No algorithm found for key $key")
|
||||
|
||||
val algorithm =
|
||||
leafAlgorithms.remove(key)
|
||||
?: throw UnsupportedOperationException("No algorithm found for key $key")
|
||||
|
||||
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
|
||||
val keybox = KeyBoxUtils.getKeybox(keyboxFileName, algorithm)
|
||||
?: throw UnsupportedOperationException("Unsupported algorithm '$algorithm' in keybox '$keyboxFileName'")
|
||||
|
||||
val keybox =
|
||||
KeyBoxUtils.getKeybox(keyboxFileName, algorithm)
|
||||
?: throw UnsupportedOperationException(
|
||||
"Unsupported algorithm '$algorithm' in keybox '$keyboxFileName'"
|
||||
)
|
||||
|
||||
CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to hack CA certificate chain for uid=$uid", t)
|
||||
caList
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun hackUserCertificate(certificate: ByteArray?, alias: String, uid: Int): ByteArray {
|
||||
if (certificate == null) {
|
||||
throw UnsupportedOperationException("Leaf certificate is null!")
|
||||
}
|
||||
|
||||
|
||||
return try {
|
||||
val leaf = certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(certificate)
|
||||
) as X509Certificate
|
||||
|
||||
val extensionBytes = leaf.getExtensionValue(ATTESTATION_OID.id)
|
||||
?: return certificate // No attestation extension, return original
|
||||
|
||||
val leaf =
|
||||
certificateFactory.generateCertificate(ByteArrayInputStream(certificate))
|
||||
as X509Certificate
|
||||
|
||||
val extensionBytes =
|
||||
leaf.getExtensionValue(ATTESTATION_OID.id)
|
||||
?: return certificate // No attestation extension, return original
|
||||
|
||||
val keyIdentifier = KeyIdentifier(alias, uid)
|
||||
leafAlgorithms[keyIdentifier] = leaf.publicKey.algorithm
|
||||
|
||||
|
||||
val leafHolder = X509CertificateHolder(leaf.encoded)
|
||||
val extension = leafHolder.getExtension(ATTESTATION_OID)
|
||||
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||
val encodables = sequence.toArray()
|
||||
val teeEnforced = encodables[7] as ASN1Sequence
|
||||
|
||||
|
||||
val vector = ASN1EncodableVector()
|
||||
var rootOfTrust: ASN1Encodable? = null
|
||||
|
||||
|
||||
teeEnforced.forEach { element ->
|
||||
val taggedObject = element as ASN1TaggedObject
|
||||
if (taggedObject.tagNo == 704) {
|
||||
@@ -168,26 +179,30 @@ object CertificateHack {
|
||||
vector.add(taggedObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
|
||||
val algorithmName = leaf.publicKey.algorithm
|
||||
val keybox = KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
|
||||
?: throw UnsupportedOperationException("Unsupported algorithm '$algorithmName' in keybox '$keyboxFileName'")
|
||||
|
||||
val builder = X509v3CertificateBuilder(
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject,
|
||||
leafHolder.serialNumber,
|
||||
leafHolder.notBefore,
|
||||
leafHolder.notAfter,
|
||||
leafHolder.subject,
|
||||
leafHolder.subjectPublicKeyInfo
|
||||
)
|
||||
|
||||
val keybox =
|
||||
KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
|
||||
?: throw UnsupportedOperationException(
|
||||
"Unsupported algorithm '$algorithmName' in keybox '$keyboxFileName'"
|
||||
)
|
||||
|
||||
val builder =
|
||||
X509v3CertificateBuilder(
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject,
|
||||
leafHolder.serialNumber,
|
||||
leafHolder.notBefore,
|
||||
leafHolder.notAfter,
|
||||
leafHolder.subject,
|
||||
leafHolder.subjectPublicKeyInfo,
|
||||
)
|
||||
|
||||
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
|
||||
|
||||
|
||||
val hackedExtension = hackAttestExtension(rootOfTrust, vector, encodables)
|
||||
builder.addExtension(hackedExtension)
|
||||
|
||||
|
||||
leafHolder.extensions.extensionOIDs.forEach { oid ->
|
||||
if (oid.id != ATTESTATION_OID.id) {
|
||||
builder.addExtension(leafHolder.getExtension(oid))
|
||||
@@ -200,47 +215,52 @@ object CertificateHack {
|
||||
certificate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun hackAttestExtension(
|
||||
originalRootOfTrust: ASN1Encodable?,
|
||||
vector: ASN1EncodableVector,
|
||||
originalEncodables: Array<ASN1Encodable>
|
||||
originalEncodables: Array<ASN1Encodable>,
|
||||
): Extension {
|
||||
val verifiedBootKey = AndroidUtils.bootKey
|
||||
var verifiedBootHash: ByteArray? = null
|
||||
|
||||
|
||||
try {
|
||||
if (originalRootOfTrust is ASN1Sequence) {
|
||||
verifiedBootHash = CertificateUtils.getByteArrayFromAsn1(originalRootOfTrust.getObjectAt(3))
|
||||
verifiedBootHash =
|
||||
CertificateUtils.getByteArrayFromAsn1(originalRootOfTrust.getObjectAt(3))
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Failed to get verified boot hash from original, using generated", t)
|
||||
}
|
||||
|
||||
|
||||
if (verifiedBootHash == null) {
|
||||
verifiedBootHash = AndroidUtils.getBootHashFromProp()
|
||||
}
|
||||
|
||||
val rootOfTrustElements = arrayOf(
|
||||
DEROctetString(verifiedBootKey),
|
||||
ASN1Boolean.TRUE,
|
||||
ASN1Enumerated(0),
|
||||
DEROctetString(verifiedBootHash)
|
||||
)
|
||||
|
||||
val rootOfTrustElements =
|
||||
arrayOf(
|
||||
DEROctetString(verifiedBootKey),
|
||||
ASN1Boolean.TRUE,
|
||||
ASN1Enumerated(0),
|
||||
DEROctetString(verifiedBootHash),
|
||||
)
|
||||
val hackedRootOfTrust = DERSequence(rootOfTrustElements)
|
||||
|
||||
vector.add(DERTaggedObject(true, 718, ASN1Integer(AndroidUtils.vendorPatchLevelLong.toLong())))
|
||||
vector.add(DERTaggedObject(true, 719, ASN1Integer(AndroidUtils.bootPatchLevelLong.toLong())))
|
||||
|
||||
vector.add(
|
||||
DERTaggedObject(true, 718, ASN1Integer(AndroidUtils.vendorPatchLevelLong.toLong()))
|
||||
)
|
||||
vector.add(
|
||||
DERTaggedObject(true, 719, ASN1Integer(AndroidUtils.bootPatchLevelLong.toLong()))
|
||||
)
|
||||
vector.add(DERTaggedObject(true, 706, ASN1Integer(AndroidUtils.patchLevel.toLong())))
|
||||
vector.add(DERTaggedObject(true, 705, ASN1Integer(AndroidUtils.osVersion.toLong())))
|
||||
vector.add(DERTaggedObject(704, hackedRootOfTrust))
|
||||
|
||||
|
||||
val hackEnforced = DERSequence(vector)
|
||||
originalEncodables[7] = hackEnforced
|
||||
val hackedSequence = DERSequence(originalEncodables)
|
||||
val hackedSequenceOctets = DEROctetString(hackedSequence)
|
||||
|
||||
|
||||
return Extension(ATTESTATION_OID, false, hackedSequenceOctets)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,12 +9,6 @@ import android.system.keystore2.KeyEntryResponse
|
||||
import android.system.keystore2.KeyMetadata
|
||||
import android.util.Log
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.DEROctetString
|
||||
import org.bouncycastle.openssl.PEMKeyPair
|
||||
import org.bouncycastle.openssl.PEMParser
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.StringReader
|
||||
@@ -24,30 +18,41 @@ import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.CertificateParsingException
|
||||
import java.security.cert.X509Certificate
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.DEROctetString
|
||||
import org.bouncycastle.openssl.PEMKeyPair
|
||||
import org.bouncycastle.openssl.PEMParser
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
|
||||
object CertificateUtils {
|
||||
private const val TAG = "TrickyStoreOSS"
|
||||
|
||||
|
||||
sealed class CertificateResult<out T> {
|
||||
data class Success<T>(val data: T) : CertificateResult<T>()
|
||||
data class Error(val message: String, val cause: Throwable? = null) : CertificateResult<Nothing>()
|
||||
|
||||
inline fun <R> map(transform: (T) -> R): CertificateResult<R> = when (this) {
|
||||
is Success -> Success(transform(data))
|
||||
is Error -> this
|
||||
}
|
||||
|
||||
fun getOrNull(): T? = when (this) {
|
||||
is Success -> data
|
||||
is Error -> null
|
||||
}
|
||||
|
||||
data class Error(val message: String, val cause: Throwable? = null) :
|
||||
CertificateResult<Nothing>()
|
||||
|
||||
inline fun <R> map(transform: (T) -> R): CertificateResult<R> =
|
||||
when (this) {
|
||||
is Success -> Success(transform(data))
|
||||
is Error -> this
|
||||
}
|
||||
|
||||
fun getOrNull(): T? =
|
||||
when (this) {
|
||||
is Success -> data
|
||||
is Error -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class ParseResult<out T> {
|
||||
data class Success<T>(val data: T) : ParseResult<T>()
|
||||
|
||||
data class Error(val message: String, val cause: Throwable? = null) : ParseResult<Nothing>()
|
||||
}
|
||||
|
||||
|
||||
fun ByteArray?.toCertificate(): X509Certificate? {
|
||||
return this?.let { bytes ->
|
||||
try {
|
||||
@@ -59,73 +64,74 @@ object CertificateUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> {
|
||||
return try {
|
||||
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)
|
||||
} catch (e: CertificateException) {
|
||||
CertificateResult.Error("Failed to parse certificate", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun ByteArray?.toCertificates(): Collection<X509Certificate> {
|
||||
return this?.let { bytes ->
|
||||
try {
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
certFactory.generateCertificates(ByteArrayInputStream(bytes)) as Collection<X509Certificate>
|
||||
certFactory.generateCertificates(ByteArrayInputStream(bytes))
|
||||
as Collection<X509Certificate>
|
||||
} catch (e: CertificateException) {
|
||||
Log.w(TAG, "Couldn't parse certificates in keystore", e)
|
||||
emptyList()
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun Collection<Certificate>.toByteArray(): ByteArray? = runCatching {
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
forEach { cert -> outputStream.write(cert.encoded) }
|
||||
outputStream.toByteArray()
|
||||
}
|
||||
}.onFailure {
|
||||
Log.w(TAG, "Failed to convert certificates to byte array", it)
|
||||
}.getOrNull()
|
||||
|
||||
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? = runCatching {
|
||||
map { it.encoded }
|
||||
}.onFailure {
|
||||
Log.w(TAG, "Failed to convert certificates to byte array list", it)
|
||||
}.getOrNull()
|
||||
|
||||
|
||||
fun Collection<Certificate>.toByteArray(): ByteArray? =
|
||||
runCatching {
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
forEach { cert -> outputStream.write(cert.encoded) }
|
||||
outputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
.onFailure { Log.w(TAG, "Failed to convert certificates to byte array", it) }
|
||||
.getOrNull()
|
||||
|
||||
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? =
|
||||
runCatching { map { it.encoded } }
|
||||
.onFailure { Log.w(TAG, "Failed to convert certificates to byte array list", it) }
|
||||
.getOrNull()
|
||||
|
||||
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
|
||||
val metadata = this?.metadata ?: return null
|
||||
val leafCert = metadata.certificate?.toCertificate() ?: return null
|
||||
|
||||
|
||||
return when (val chainBytes = metadata.certificateChain) {
|
||||
null -> arrayOf(leafCert)
|
||||
else -> {
|
||||
val additionalCerts = chainBytes.toCertificates()
|
||||
buildList {
|
||||
add(leafCert)
|
||||
addAll(additionalCerts)
|
||||
}.toTypedArray()
|
||||
add(leafCert)
|
||||
addAll(additionalCerts)
|
||||
}
|
||||
.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
metadata.putCertificateChain(chain)
|
||||
}
|
||||
return runCatching { metadata.putCertificateChain(chain) }
|
||||
}
|
||||
|
||||
|
||||
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
if (chain.isEmpty()) return@runCatching
|
||||
|
||||
|
||||
certificate = chain[0].encoded
|
||||
|
||||
|
||||
if (chain.size > 1) {
|
||||
ByteArrayOutputStream().use { output ->
|
||||
for (i in 1 until chain.size) {
|
||||
@@ -138,7 +144,7 @@ object CertificateUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Certificate parsing utilities
|
||||
fun parseKeyPair(keyContent: String): ParseResult<PEMKeyPair> {
|
||||
return try {
|
||||
@@ -154,54 +160,59 @@ object CertificateUtils {
|
||||
ParseResult.Error("Failed to parse PEM key pair", t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun parseCertificate(certContent: String): ParseResult<Certificate> {
|
||||
return try {
|
||||
PemReader(StringReader(certContent.trimLine())).use { reader ->
|
||||
val pemObject = reader.readPemObject()
|
||||
val certificate = CertificateFactory.getInstance("X.509").generateCertificate(
|
||||
ByteArrayInputStream(pemObject.content)
|
||||
)
|
||||
val certificate =
|
||||
CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(ByteArrayInputStream(pemObject.content))
|
||||
ParseResult.Success(certificate)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
ParseResult.Error("Failed to parse certificate", t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun convertPemToKeyPair(pemKeyPair: PEMKeyPair): KeyPair {
|
||||
return JcaPEMKeyConverter().getKeyPair(pemKeyPair)
|
||||
}
|
||||
|
||||
|
||||
@Throws(CertificateParsingException::class)
|
||||
fun getByteArrayFromAsn1(asn1Encodable: ASN1Encodable): ByteArray {
|
||||
return when (asn1Encodable) {
|
||||
is DEROctetString -> asn1Encodable.octets
|
||||
else -> throw CertificateParsingException("Expected DEROctetString, got ${asn1Encodable::class.simpleName}")
|
||||
else ->
|
||||
throw CertificateParsingException(
|
||||
"Expected DEROctetString, got ${asn1Encodable::class.simpleName}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
return runCatching {
|
||||
metadata.putCertificateChain(chain).getOrThrow()
|
||||
}
|
||||
return runCatching { metadata.putCertificateChain(chain).getOrThrow() }
|
||||
}
|
||||
|
||||
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
if (chain.isEmpty()) return@runCatching
|
||||
|
||||
|
||||
certificate = chain[0].encoded
|
||||
|
||||
|
||||
if (chain.size > 1) {
|
||||
ByteArrayOutputStream().use { output ->
|
||||
for (i in 1 until chain.size) {
|
||||
@@ -213,4 +224,4 @@ fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
|
||||
certificateChain = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ private const val SERVICE_SLEEP_MS = 1000000L
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
Logger.i("Welcome to TrickyStoreOSS!")
|
||||
|
||||
|
||||
try {
|
||||
AndroidUtils.setupBootHash()
|
||||
initializeInterceptors()
|
||||
@@ -29,26 +29,27 @@ fun main(args: Array<String>) {
|
||||
|
||||
private fun initializeInterceptors() {
|
||||
val interceptor = selectKeystoreInterceptor()
|
||||
|
||||
|
||||
while (!interceptor.tryRunKeystoreInterceptor()) {
|
||||
Logger.d("Retrying interceptor initialization...")
|
||||
Thread.sleep(RETRY_DELAY_MS)
|
||||
}
|
||||
|
||||
|
||||
PkgConfig.initialize()
|
||||
Logger.i("Interceptors initialized successfully")
|
||||
}
|
||||
|
||||
private fun selectKeystoreInterceptor() = when {
|
||||
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
|
||||
Logger.i("Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})")
|
||||
KeystoreInterceptor
|
||||
private fun selectKeystoreInterceptor() =
|
||||
when {
|
||||
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
|
||||
Logger.i("Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})")
|
||||
KeystoreInterceptor
|
||||
}
|
||||
else -> {
|
||||
Logger.i("Using Keystore2Interceptor for Android S+ (SDK ${Build.VERSION.SDK_INT})")
|
||||
Keystore2Interceptor
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Logger.i("Using Keystore2Interceptor for Android S+ (SDK ${Build.VERSION.SDK_INT})")
|
||||
Keystore2Interceptor
|
||||
}
|
||||
}
|
||||
|
||||
private fun maintainService() {
|
||||
Logger.i("Service started, entering maintenance mode")
|
||||
|
||||
@@ -10,29 +10,30 @@ import io.github.beakthoven.TrickyStoreOSS.CertificateGen.KeyBox
|
||||
import io.github.beakthoven.TrickyStoreOSS.CertificateHack.clearLeafAlgorithms
|
||||
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
|
||||
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.StringReader
|
||||
import java.security.cert.Certificate
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
|
||||
class XmlParser(private val xmlContent: String) {
|
||||
|
||||
|
||||
sealed class ParseResult {
|
||||
data class Success(val attributes: Map<String, String>) : ParseResult()
|
||||
|
||||
data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
|
||||
}
|
||||
|
||||
|
||||
fun obtainPath(path: String): ParseResult {
|
||||
return try {
|
||||
val factory = XmlPullParserFactory.newInstance()
|
||||
val parser = factory.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(StringReader(xmlContent))
|
||||
|
||||
|
||||
val tags = path.split(".").toTypedArray()
|
||||
val result = readData(parser, tags, 0, mutableMapOf())
|
||||
ParseResult.Success(result)
|
||||
@@ -44,7 +45,7 @@ class XmlParser(private val xmlContent: String) {
|
||||
ParseResult.Error("Unexpected error: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun obtainPathLegacy(path: String): Map<String, String> {
|
||||
when (val result = obtainPath(path)) {
|
||||
@@ -52,24 +53,24 @@ class XmlParser(private val xmlContent: String) {
|
||||
is ParseResult.Error -> throw result.cause ?: Exception(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readData(
|
||||
parser: XmlPullParser,
|
||||
tags: Array<String>,
|
||||
index: Int,
|
||||
tagCounts: MutableMap<String, Int>
|
||||
tagCounts: MutableMap<String, Int>,
|
||||
): Map<String, String> {
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
val currentTag = parser.name ?: continue
|
||||
val targetTag = tags[index]
|
||||
val tagParts = targetTag.split("[")
|
||||
val baseTagName = tagParts[0]
|
||||
|
||||
|
||||
if (currentTag == baseTagName) {
|
||||
return if (tagParts.size > 1) {
|
||||
handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
|
||||
@@ -80,10 +81,10 @@ class XmlParser(private val xmlContent: String) {
|
||||
skipCurrentElement(parser)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun handleIndexedTag(
|
||||
parser: XmlPullParser,
|
||||
@@ -91,13 +92,14 @@ class XmlParser(private val xmlContent: String) {
|
||||
index: Int,
|
||||
tagCounts: MutableMap<String, Int>,
|
||||
currentTag: String,
|
||||
indexPart: String
|
||||
indexPart: String,
|
||||
): Map<String, String> {
|
||||
val targetIndex = indexPart.replace("]", "").toIntOrNull()
|
||||
?: throw XmlPullParserException("Invalid index in tag: $indexPart")
|
||||
|
||||
val targetIndex =
|
||||
indexPart.replace("]", "").toIntOrNull()
|
||||
?: throw XmlPullParserException("Invalid index in tag: $indexPart")
|
||||
|
||||
val currentCount = tagCounts.getOrDefault(currentTag, 0)
|
||||
|
||||
|
||||
return if (currentCount < targetIndex) {
|
||||
tagCounts[currentTag] = currentCount + 1
|
||||
readData(parser, tags, index, tagCounts)
|
||||
@@ -109,12 +111,12 @@ class XmlParser(private val xmlContent: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun handleRegularTag(
|
||||
parser: XmlPullParser,
|
||||
tags: Array<String>,
|
||||
index: Int
|
||||
index: Int,
|
||||
): Map<String, String> {
|
||||
return if (index == tags.size - 1) {
|
||||
readAttributes(parser)
|
||||
@@ -122,11 +124,11 @@ class XmlParser(private val xmlContent: String) {
|
||||
readData(parser, tags, index + 1, mutableMapOf())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readAttributes(parser: XmlPullParser): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
|
||||
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
val name = parser.getAttributeName(i)
|
||||
val value = parser.getAttributeValue(i)
|
||||
@@ -134,22 +136,20 @@ class XmlParser(private val xmlContent: String) {
|
||||
attributes[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
parser.text?.let { text ->
|
||||
attributes["text"] = text
|
||||
}
|
||||
parser.text?.let { text -> attributes["text"] = text }
|
||||
}
|
||||
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun skipCurrentElement(parser: XmlPullParser) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
throw IllegalStateException("Parser must be positioned at START_TAG")
|
||||
}
|
||||
|
||||
|
||||
var depth = 1
|
||||
while (depth != 0) {
|
||||
when (parser.next()) {
|
||||
@@ -164,18 +164,19 @@ object KeyBoxUtils {
|
||||
private val loadedKeyboxFiles = ConcurrentHashMap<String, ConcurrentHashMap<String, KeyBox>>()
|
||||
|
||||
/**
|
||||
* The primary public function to get a specific KeyBox for a given algorithm and file.
|
||||
* It will load and cache the file on demand if it hasn't been seen before.
|
||||
* The primary public function to get a specific KeyBox for a given algorithm and file. It will
|
||||
* load and cache the file on demand if it hasn't been seen before.
|
||||
*
|
||||
* @param keyboxFileName The simple name of the keybox file (e.g., "keybox.xml").
|
||||
* @param algorithm The algorithm key (e.g., KeyProperties.KEY_ALGORITHM_EC).
|
||||
* @return The requested KeyBox, or null if not found in the specified file.
|
||||
*/
|
||||
fun getKeybox(keyboxFileName: String, algorithm: String): KeyBox? {
|
||||
val keyboxesForFile = loadedKeyboxFiles.getOrPut(keyboxFileName) {
|
||||
// If this file is not in our cache, load it now.
|
||||
readFromFile(keyboxFileName)
|
||||
}
|
||||
val keyboxesForFile =
|
||||
loadedKeyboxFiles.getOrPut(keyboxFileName) {
|
||||
// If this file is not in our cache, load it now.
|
||||
readFromFile(keyboxFileName)
|
||||
}
|
||||
Logger.i("Retriving keybox $keyboxFileName [$algorithm]")
|
||||
return keyboxesForFile[algorithm]
|
||||
}
|
||||
@@ -196,10 +197,16 @@ object KeyBoxUtils {
|
||||
val xmlParser = XmlParser(xmlData.sanitizeXml())
|
||||
|
||||
val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
|
||||
val numberOfKeyboxes = when (numberOfKeyboxesResult) {
|
||||
is XmlParser.ParseResult.Success -> numberOfKeyboxesResult.attributes["text"]?.toIntOrNull() ?: 1
|
||||
is XmlParser.ParseResult.Error -> throw Exception(numberOfKeyboxesResult.message, numberOfKeyboxesResult.cause)
|
||||
}
|
||||
val numberOfKeyboxes =
|
||||
when (numberOfKeyboxesResult) {
|
||||
is XmlParser.ParseResult.Success ->
|
||||
numberOfKeyboxesResult.attributes["text"]?.toIntOrNull() ?: 1
|
||||
is XmlParser.ParseResult.Error ->
|
||||
throw Exception(
|
||||
numberOfKeyboxesResult.message,
|
||||
numberOfKeyboxesResult.cause,
|
||||
)
|
||||
}
|
||||
|
||||
repeat(numberOfKeyboxes) { i ->
|
||||
val (algorithmName, keyBox) = processKeybox(xmlParser, i)
|
||||
@@ -214,9 +221,11 @@ object KeyBoxUtils {
|
||||
return keyboxes
|
||||
}
|
||||
|
||||
fun hasKeyboxes(): Boolean = loadedKeyboxFiles.isNotEmpty() && loadedKeyboxFiles.values.any { it.isNotEmpty() }
|
||||
fun hasKeyboxes(): Boolean =
|
||||
loadedKeyboxFiles.isNotEmpty() && loadedKeyboxFiles.values.any { it.isNotEmpty() }
|
||||
|
||||
// This function is now deprecated and should be removed. We keep it for now to show the transition.
|
||||
// This function is now deprecated and should be removed. We keep it for now to show the
|
||||
// transition.
|
||||
// Its logic is now inside readFromFile.
|
||||
@Deprecated("Use getKeybox(fileName, algorithm) instead for dynamic loading.")
|
||||
fun readFromXml(xmlData: String?) {
|
||||
@@ -224,26 +233,30 @@ object KeyBoxUtils {
|
||||
// We could make this load into a default keybox for backward compatibility if needed.
|
||||
loadedKeyboxFiles.clear()
|
||||
clearLeafAlgorithms()
|
||||
|
||||
|
||||
if (xmlData == null) {
|
||||
Logger.i("Clearing all keyboxes")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
val xmlParser = XmlParser(xmlData.sanitizeXml())
|
||||
|
||||
|
||||
val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
|
||||
val numberOfKeyboxes = when (numberOfKeyboxesResult) {
|
||||
is XmlParser.ParseResult.Success -> numberOfKeyboxesResult.attributes["text"]?.toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid number of keyboxes")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(numberOfKeyboxesResult.message, numberOfKeyboxesResult.cause)
|
||||
}
|
||||
|
||||
repeat(numberOfKeyboxes) { i ->
|
||||
processKeybox(xmlParser, i)
|
||||
}
|
||||
|
||||
val numberOfKeyboxes =
|
||||
when (numberOfKeyboxesResult) {
|
||||
is XmlParser.ParseResult.Success ->
|
||||
numberOfKeyboxesResult.attributes["text"]?.toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid number of keyboxes")
|
||||
is XmlParser.ParseResult.Error ->
|
||||
throw Exception(
|
||||
numberOfKeyboxesResult.message,
|
||||
numberOfKeyboxesResult.cause,
|
||||
)
|
||||
}
|
||||
|
||||
repeat(numberOfKeyboxes) { i -> processKeybox(xmlParser, i) }
|
||||
|
||||
Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Error loading XML file (keyboxes cleared)", t)
|
||||
@@ -253,11 +266,7 @@ object KeyBoxUtils {
|
||||
private fun String.sanitizeXml(): String {
|
||||
var content = this
|
||||
|
||||
val boms = listOf(
|
||||
"\uFEFF",
|
||||
"\uFFFE",
|
||||
"\u0000\uFEFF"
|
||||
)
|
||||
val boms = listOf("\uFEFF", "\uFFFE", "\u0000\uFEFF")
|
||||
content = content.trimStart()
|
||||
for (bom in boms) {
|
||||
content = content.removePrefix(bom)
|
||||
@@ -270,60 +279,82 @@ object KeyBoxUtils {
|
||||
private fun processKeybox(xmlParser: XmlParser, index: Int): Pair<String, KeyBox> {
|
||||
try {
|
||||
val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
|
||||
val keyboxAlgorithm = when (algorithmResult) {
|
||||
is XmlParser.ParseResult.Success -> algorithmResult.attributes["algorithm"]
|
||||
?: throw IllegalArgumentException("Missing algorithm attribute")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(algorithmResult.message, algorithmResult.cause)
|
||||
}
|
||||
|
||||
val privateKeyResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
|
||||
val privateKeyContent = when (privateKeyResult) {
|
||||
is XmlParser.ParseResult.Success -> privateKeyResult.attributes["text"]
|
||||
?: throw IllegalArgumentException("Missing private key text")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(privateKeyResult.message, privateKeyResult.cause)
|
||||
}
|
||||
|
||||
val numberOfCertificatesResult = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
|
||||
)
|
||||
val numberOfCertificates = when (numberOfCertificatesResult) {
|
||||
is XmlParser.ParseResult.Success -> numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid number of certificates")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(numberOfCertificatesResult.message, numberOfCertificatesResult.cause)
|
||||
}
|
||||
|
||||
val keyboxAlgorithm =
|
||||
when (algorithmResult) {
|
||||
is XmlParser.ParseResult.Success ->
|
||||
algorithmResult.attributes["algorithm"]
|
||||
?: throw IllegalArgumentException("Missing algorithm attribute")
|
||||
is XmlParser.ParseResult.Error ->
|
||||
throw Exception(algorithmResult.message, algorithmResult.cause)
|
||||
}
|
||||
|
||||
val privateKeyResult =
|
||||
xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
|
||||
val privateKeyContent =
|
||||
when (privateKeyResult) {
|
||||
is XmlParser.ParseResult.Success ->
|
||||
privateKeyResult.attributes["text"]
|
||||
?: throw IllegalArgumentException("Missing private key text")
|
||||
is XmlParser.ParseResult.Error ->
|
||||
throw Exception(privateKeyResult.message, privateKeyResult.cause)
|
||||
}
|
||||
|
||||
val numberOfCertificatesResult =
|
||||
xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
|
||||
)
|
||||
val numberOfCertificates =
|
||||
when (numberOfCertificatesResult) {
|
||||
is XmlParser.ParseResult.Success ->
|
||||
numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid number of certificates")
|
||||
is XmlParser.ParseResult.Error ->
|
||||
throw Exception(
|
||||
numberOfCertificatesResult.message,
|
||||
numberOfCertificatesResult.cause,
|
||||
)
|
||||
}
|
||||
|
||||
val certificateChain = mutableListOf<Certificate>()
|
||||
repeat(numberOfCertificates) { j ->
|
||||
val certResult = xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[$index].CertificateChain.Certificate[$j]"
|
||||
)
|
||||
val certContent = when (certResult) {
|
||||
is XmlParser.ParseResult.Success -> certResult.attributes["text"]
|
||||
?: throw IllegalArgumentException("Missing certificate text")
|
||||
is XmlParser.ParseResult.Error -> throw Exception(certResult.message, certResult.cause)
|
||||
}
|
||||
|
||||
val certResult =
|
||||
xmlParser.obtainPath(
|
||||
"AndroidAttestation.Keybox.Key[$index].CertificateChain.Certificate[$j]"
|
||||
)
|
||||
val certContent =
|
||||
when (certResult) {
|
||||
is XmlParser.ParseResult.Success ->
|
||||
certResult.attributes["text"]
|
||||
?: throw IllegalArgumentException("Missing certificate text")
|
||||
is XmlParser.ParseResult.Error ->
|
||||
throw Exception(certResult.message, certResult.cause)
|
||||
}
|
||||
|
||||
when (val certParseResult = CertificateUtils.parseCertificate(certContent)) {
|
||||
is CertificateUtils.ParseResult.Success -> certificateChain.add(certParseResult.data)
|
||||
is CertificateUtils.ParseResult.Error -> throw Exception(certParseResult.message, certParseResult.cause)
|
||||
is CertificateUtils.ParseResult.Success ->
|
||||
certificateChain.add(certParseResult.data)
|
||||
is CertificateUtils.ParseResult.Error ->
|
||||
throw Exception(certParseResult.message, certParseResult.cause)
|
||||
}
|
||||
}
|
||||
|
||||
val pemKeyPair = when (val keyParseResult = CertificateUtils.parseKeyPair(privateKeyContent)) {
|
||||
is CertificateUtils.ParseResult.Success -> keyParseResult.data
|
||||
is CertificateUtils.ParseResult.Error -> throw Exception(keyParseResult.message, keyParseResult.cause)
|
||||
}
|
||||
|
||||
|
||||
val pemKeyPair =
|
||||
when (val keyParseResult = CertificateUtils.parseKeyPair(privateKeyContent)) {
|
||||
is CertificateUtils.ParseResult.Success -> keyParseResult.data
|
||||
is CertificateUtils.ParseResult.Error ->
|
||||
throw Exception(keyParseResult.message, keyParseResult.cause)
|
||||
}
|
||||
|
||||
val keyPair = CertificateUtils.convertPemToKeyPair(pemKeyPair)
|
||||
|
||||
val algorithmName = when (keyboxAlgorithm.lowercase()) {
|
||||
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
|
||||
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
else -> keyboxAlgorithm
|
||||
}
|
||||
|
||||
|
||||
val algorithmName =
|
||||
when (keyboxAlgorithm.lowercase()) {
|
||||
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
|
||||
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
else -> keyboxAlgorithm
|
||||
}
|
||||
|
||||
return algorithmName to KeyBox(pemKeyPair, keyPair, certificateChain)
|
||||
|
||||
} catch (t: Throwable) {
|
||||
Logger.e("Error processing keybox $index", t)
|
||||
throw t
|
||||
|
||||
@@ -25,71 +25,79 @@ object PkgConfig {
|
||||
private val keyboxRegex = Regex("^\\[([a-zA-Z0-9_.-]+\\.xml)]$")
|
||||
private const val DEFAULT_KEYBOX_FILE = "keybox.xml"
|
||||
|
||||
fun getKeyboxFileForUid(callingUid: Int): String = runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return DEFAULT_KEYBOX_FILE
|
||||
for (pkg in ps) {
|
||||
packageKeyboxes[pkg]?.let { return it }
|
||||
}
|
||||
return DEFAULT_KEYBOX_FILE
|
||||
}.getOrDefault(DEFAULT_KEYBOX_FILE)
|
||||
fun getKeyboxFileForUid(callingUid: Int): String =
|
||||
runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return DEFAULT_KEYBOX_FILE
|
||||
for (pkg in ps) {
|
||||
packageKeyboxes[pkg]?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
return DEFAULT_KEYBOX_FILE
|
||||
}
|
||||
.getOrDefault(DEFAULT_KEYBOX_FILE)
|
||||
|
||||
enum class Mode {
|
||||
AUTO, LEAF_HACK, GENERATE
|
||||
AUTO,
|
||||
LEAF_HACK,
|
||||
GENERATE,
|
||||
}
|
||||
|
||||
private fun updateTargetPackages(f: File?) = runCatching {
|
||||
hackPackages.clear()
|
||||
generatePackages.clear()
|
||||
packageModes.clear()
|
||||
packageKeyboxes.clear()
|
||||
private fun updateTargetPackages(f: File?) =
|
||||
runCatching {
|
||||
hackPackages.clear()
|
||||
generatePackages.clear()
|
||||
packageModes.clear()
|
||||
packageKeyboxes.clear()
|
||||
|
||||
var currentKeyboxFile = DEFAULT_KEYBOX_FILE
|
||||
var currentKeyboxFile = DEFAULT_KEYBOX_FILE
|
||||
|
||||
f?.readLines()?.forEach { line ->
|
||||
val n = line.trim()
|
||||
if (n.isBlank() || n.startsWith("#")) {
|
||||
return@forEach // Skip comments and empty lines
|
||||
}
|
||||
f?.readLines()?.forEach { line ->
|
||||
val n = line.trim()
|
||||
if (n.isBlank() || n.startsWith("#")) {
|
||||
return@forEach // Skip comments and empty lines
|
||||
}
|
||||
|
||||
val matchResult = keyboxRegex.find(n)
|
||||
if (matchResult != null) {
|
||||
currentKeyboxFile = matchResult.groupValues[1]
|
||||
Logger.i("Switched to keybox file: $currentKeyboxFile for subsequent packages")
|
||||
return@forEach
|
||||
}
|
||||
val matchResult = keyboxRegex.find(n)
|
||||
if (matchResult != null) {
|
||||
currentKeyboxFile = matchResult.groupValues[1]
|
||||
Logger.i(
|
||||
"Switched to keybox file: $currentKeyboxFile for subsequent packages"
|
||||
)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
when {
|
||||
n.endsWith("!") -> {
|
||||
val pkg = n.removeSuffix("!").trim()
|
||||
generatePackages.add(pkg)
|
||||
packageModes[pkg] = Mode.GENERATE
|
||||
packageKeyboxes[pkg] = currentKeyboxFile
|
||||
}
|
||||
n.endsWith("?") -> {
|
||||
val pkg = n.removeSuffix("?").trim()
|
||||
hackPackages.add(pkg)
|
||||
packageModes[pkg] = Mode.LEAF_HACK
|
||||
packageKeyboxes[pkg] = currentKeyboxFile
|
||||
}
|
||||
else -> {
|
||||
// Auto mode
|
||||
packageModes[n] = Mode.AUTO
|
||||
packageKeyboxes[n] = currentKeyboxFile
|
||||
when {
|
||||
n.endsWith("!") -> {
|
||||
val pkg = n.removeSuffix("!").trim()
|
||||
generatePackages.add(pkg)
|
||||
packageModes[pkg] = Mode.GENERATE
|
||||
packageKeyboxes[pkg] = currentKeyboxFile
|
||||
}
|
||||
n.endsWith("?") -> {
|
||||
val pkg = n.removeSuffix("?").trim()
|
||||
hackPackages.add(pkg)
|
||||
packageModes[pkg] = Mode.LEAF_HACK
|
||||
packageKeyboxes[pkg] = currentKeyboxFile
|
||||
}
|
||||
else -> {
|
||||
// Auto mode
|
||||
packageModes[n] = Mode.AUTO
|
||||
packageKeyboxes[n] = currentKeyboxFile
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.i(
|
||||
"update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes, , packageKeyboxes=$packageKeyboxes"
|
||||
)
|
||||
}
|
||||
}
|
||||
Logger.i("update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes, , packageKeyboxes=$packageKeyboxes")
|
||||
}.onFailure {
|
||||
Logger.e("failed to update target files", it)
|
||||
}
|
||||
.onFailure { Logger.e("failed to update target files", it) }
|
||||
|
||||
// This function is now deprecated in favor of a more dynamic approach, but kept for simplicity.
|
||||
// The key logic is now in KeyBoxUtils which will be called from the interceptors.
|
||||
private fun updateKeyBox(f: File?) = runCatching {
|
||||
KeyBoxUtils.readFromXml(f?.readText())
|
||||
}.onFailure {
|
||||
Logger.e("failed to update keybox", it)
|
||||
}
|
||||
private fun updateKeyBox(f: File?) =
|
||||
runCatching { KeyBoxUtils.readFromXml(f?.readText()) }
|
||||
.onFailure { Logger.e("failed to update keybox", it) }
|
||||
|
||||
const val CONFIG_PATH = "/data/adb/tricky_store"
|
||||
private const val TARGET_FILE = "target.txt"
|
||||
@@ -97,15 +105,14 @@ object PkgConfig {
|
||||
private const val PATCHLEVEL_FILE = "security_patch.txt"
|
||||
private val root = File(CONFIG_PATH)
|
||||
|
||||
@Volatile
|
||||
private var teeBroken: Boolean? = null
|
||||
@Volatile private var teeBroken: Boolean? = null
|
||||
|
||||
private fun storeTEEStatus(root: File) {
|
||||
val statusFile = File(root, TEE_STATUS_FILE)
|
||||
teeBroken = !TEEStatus
|
||||
try {
|
||||
statusFile.writeText("teeBroken=${teeBroken}")
|
||||
Logger.i("TEE status written to $statusFile: teeBroken=$teeBroken")
|
||||
Logger.i("TEE status written to $statusFile: teeBroken=$teeBroken")
|
||||
} catch (e: Exception) {
|
||||
Logger.e("Failed to write TEE status: ${e.message}")
|
||||
}
|
||||
@@ -124,16 +131,21 @@ object PkgConfig {
|
||||
object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
|
||||
override fun onEvent(event: Int, path: String?) {
|
||||
path ?: return
|
||||
val f = when (event) {
|
||||
CLOSE_WRITE, MOVED_TO -> File(root, path)
|
||||
DELETE, MOVED_FROM -> null
|
||||
else -> return
|
||||
}
|
||||
val f =
|
||||
when (event) {
|
||||
CLOSE_WRITE,
|
||||
MOVED_TO -> File(root, path)
|
||||
DELETE,
|
||||
MOVED_FROM -> null
|
||||
else -> return
|
||||
}
|
||||
when {
|
||||
path == TARGET_FILE -> updateTargetPackages(f)
|
||||
path.endsWith(".xml") -> {
|
||||
// This is a simplification. A more robust solution would be to reload the specific keybox if it's in use.
|
||||
// For now, we assume any XML change might affect the active keyboxes, prompting a reload where needed.
|
||||
// This is a simplification. A more robust solution would be to reload the
|
||||
// specific keybox if it's in use.
|
||||
// For now, we assume any XML change might affect the active keyboxes, prompting
|
||||
// a reload where needed.
|
||||
// The main logic for loading is now handled dynamically in KeyBoxUtils.
|
||||
Logger.i("Keybox file $path changed. It will be re-read on next use.")
|
||||
}
|
||||
@@ -163,12 +175,13 @@ object PkgConfig {
|
||||
}
|
||||
|
||||
private var iPm: IPackageManager? = null
|
||||
private val packageManagerDeathRecipient = object : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
(iPm as? IInterface)?.asBinder()?.unlinkToDeath(this, 0)
|
||||
iPm = null
|
||||
private val packageManagerDeathRecipient =
|
||||
object : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
(iPm as? IInterface)?.asBinder()?.unlinkToDeath(this, 0)
|
||||
iPm = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getPm(): IPackageManager? {
|
||||
if (iPm == null) {
|
||||
@@ -179,72 +192,83 @@ object PkgConfig {
|
||||
return iPm
|
||||
}
|
||||
|
||||
fun needHack(callingUid: Int): Boolean = kotlin.runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||
if (teeBroken == null) loadTEEStatus(root)
|
||||
for (pkg in ps) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.LEAF_HACK -> return true
|
||||
Mode.AUTO -> {
|
||||
if (teeBroken == false) return true
|
||||
fun needHack(callingUid: Int): Boolean =
|
||||
kotlin
|
||||
.runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||
if (teeBroken == null) loadTEEStatus(root)
|
||||
for (pkg in ps) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.LEAF_HACK -> return true
|
||||
Mode.AUTO -> {
|
||||
if (teeBroken == false) return true
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||
if (teeBroken == null) loadTEEStatus(root)
|
||||
for (pkg in ps) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.GENERATE -> return true
|
||||
Mode.AUTO -> {
|
||||
if (teeBroken == true) return true
|
||||
fun needGenerate(callingUid: Int): Boolean =
|
||||
kotlin
|
||||
.runCatching {
|
||||
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
|
||||
if (teeBroken == null) loadTEEStatus(root)
|
||||
for (pkg in ps) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.GENERATE -> return true
|
||||
Mode.AUTO -> {
|
||||
if (teeBroken == true) return true
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
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
|
||||
var _customPatchLevel: CustomPatchLevel? = null
|
||||
@Volatile var _customPatchLevel: CustomPatchLevel? = null
|
||||
|
||||
fun updatePatchLevel(f: File?) = runCatching {
|
||||
if (f == null || !f.exists()) {
|
||||
_customPatchLevel = null
|
||||
return@runCatching
|
||||
}
|
||||
val lines = f.readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
if (lines.isEmpty()) {
|
||||
_customPatchLevel = null
|
||||
return@runCatching
|
||||
}
|
||||
if (lines.size == 1 && !lines[0].contains("=")) {
|
||||
_customPatchLevel = CustomPatchLevel(all = lines[0])
|
||||
return@runCatching
|
||||
}
|
||||
val map = mutableMapOf<String, String>()
|
||||
for (line in lines) {
|
||||
val idx = line.indexOf('=')
|
||||
if (idx > 0) {
|
||||
val key = line.substring(0, idx).trim().lowercase()
|
||||
val value = line.substring(idx + 1).trim()
|
||||
map[key] = value
|
||||
fun updatePatchLevel(f: File?) =
|
||||
runCatching {
|
||||
if (f == null || !f.exists()) {
|
||||
_customPatchLevel = null
|
||||
return@runCatching
|
||||
}
|
||||
val lines =
|
||||
f.readLines()
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
if (lines.isEmpty()) {
|
||||
_customPatchLevel = null
|
||||
return@runCatching
|
||||
}
|
||||
if (lines.size == 1 && !lines[0].contains("=")) {
|
||||
_customPatchLevel = CustomPatchLevel(all = lines[0])
|
||||
return@runCatching
|
||||
}
|
||||
val map = mutableMapOf<String, String>()
|
||||
for (line in lines) {
|
||||
val idx = line.indexOf('=')
|
||||
if (idx > 0) {
|
||||
val key = line.substring(0, idx).trim().lowercase()
|
||||
val value = line.substring(idx + 1).trim()
|
||||
map[key] = value
|
||||
}
|
||||
}
|
||||
val all = map["all"]
|
||||
_customPatchLevel =
|
||||
CustomPatchLevel(
|
||||
system = map["system"] ?: all,
|
||||
vendor = map["vendor"] ?: all,
|
||||
boot = map["boot"] ?: all,
|
||||
all = all,
|
||||
)
|
||||
}
|
||||
}
|
||||
val all = map["all"]
|
||||
_customPatchLevel = CustomPatchLevel(
|
||||
system = map["system"] ?: all,
|
||||
vendor = map["vendor"] ?: all,
|
||||
boot = map["boot"] ?: all,
|
||||
all = all
|
||||
)
|
||||
}.onFailure {
|
||||
Logger.e("failed to update patch level", it)
|
||||
}
|
||||
.onFailure { Logger.e("failed to update patch level", it) }
|
||||
|
||||
private fun waitAndGetSystemService(name: String): IBinder? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
@@ -270,5 +294,5 @@ data class CustomPatchLevel(
|
||||
val system: String? = null,
|
||||
val vendor: String? = null,
|
||||
val boot: String? = null,
|
||||
val all: String? = null
|
||||
val all: String? = null,
|
||||
)
|
||||
|
||||
+69
-51
@@ -11,34 +11,34 @@ import android.os.Parcel
|
||||
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
|
||||
open class BinderInterceptor : Binder() {
|
||||
|
||||
|
||||
sealed class Result
|
||||
|
||||
|
||||
data object Skip : Result()
|
||||
|
||||
|
||||
data object Continue : Result()
|
||||
|
||||
|
||||
data class OverrideData(val data: Parcel) : Result()
|
||||
|
||||
|
||||
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
|
||||
|
||||
companion object {
|
||||
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
||||
|
||||
|
||||
private const val REGISTER_INTERCEPTOR_CODE = 1
|
||||
|
||||
|
||||
private const val PRE_TRANSACT_CODE = 1
|
||||
private const val POST_TRANSACT_CODE = 2
|
||||
|
||||
|
||||
private const val RESULT_SKIP = 1
|
||||
private const val RESULT_CONTINUE = 2
|
||||
private const val RESULT_OVERRIDE_REPLY = 3
|
||||
private const val RESULT_OVERRIDE_DATA = 4
|
||||
|
||||
|
||||
fun getBinderBackdoor(binder: IBinder): IBinder? {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
|
||||
|
||||
return try {
|
||||
val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
|
||||
if (success) {
|
||||
@@ -58,13 +58,13 @@ open class BinderInterceptor : Binder() {
|
||||
}
|
||||
|
||||
fun registerBinderInterceptor(
|
||||
backdoor: IBinder,
|
||||
target: IBinder,
|
||||
interceptor: BinderInterceptor
|
||||
backdoor: IBinder,
|
||||
target: IBinder,
|
||||
interceptor: BinderInterceptor,
|
||||
) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
|
||||
|
||||
try {
|
||||
data.writeStrongBinder(target)
|
||||
data.writeStrongBinder(interceptor)
|
||||
@@ -80,36 +80,37 @@ open class BinderInterceptor : Binder() {
|
||||
}
|
||||
|
||||
open fun onPreTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): Result = Skip
|
||||
|
||||
|
||||
open fun onPostTransact(
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int,
|
||||
): Result = Skip
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
val result = when (code) {
|
||||
PRE_TRANSACT_CODE -> handlePreTransact(data)
|
||||
POST_TRANSACT_CODE -> handlePostTransact(data)
|
||||
else -> return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
val result =
|
||||
when (code) {
|
||||
PRE_TRANSACT_CODE -> handlePreTransact(data)
|
||||
POST_TRANSACT_CODE -> handlePostTransact(data)
|
||||
else -> return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
writeResultToReply(result, reply!!)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private fun handlePreTransact(data: Parcel): Result {
|
||||
val target = data.readStrongBinder()
|
||||
val transactionCode = data.readInt()
|
||||
@@ -117,17 +118,24 @@ open class BinderInterceptor : Binder() {
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val dataSize = data.readLong()
|
||||
|
||||
|
||||
val transactionData = Parcel.obtain()
|
||||
return try {
|
||||
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
|
||||
transactionData.setDataPosition(0)
|
||||
onPreTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData)
|
||||
onPreTransact(
|
||||
target,
|
||||
transactionCode,
|
||||
transactionFlags,
|
||||
callingUid,
|
||||
callingPid,
|
||||
transactionData,
|
||||
)
|
||||
} finally {
|
||||
transactionData.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handlePostTransact(data: Parcel): Result {
|
||||
val target = data.readStrongBinder()
|
||||
val transactionCode = data.readInt()
|
||||
@@ -135,30 +143,40 @@ open class BinderInterceptor : Binder() {
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val resultCode = data.readInt()
|
||||
|
||||
|
||||
val transactionData = Parcel.obtain()
|
||||
val transactionReply = Parcel.obtain()
|
||||
|
||||
|
||||
return try {
|
||||
val dataSize = data.readLong().toInt()
|
||||
transactionData.appendFrom(data, data.dataPosition(), dataSize)
|
||||
transactionData.setDataPosition(0)
|
||||
data.setDataPosition(data.dataPosition() + dataSize)
|
||||
|
||||
|
||||
val replySize = data.readLong().toInt()
|
||||
val reply = if (replySize > 0) {
|
||||
transactionReply.appendFrom(data, data.dataPosition(), replySize)
|
||||
transactionReply.setDataPosition(0)
|
||||
transactionReply
|
||||
} else null
|
||||
|
||||
onPostTransact(target, transactionCode, transactionFlags, callingUid, callingPid, transactionData, reply, resultCode)
|
||||
val reply =
|
||||
if (replySize > 0) {
|
||||
transactionReply.appendFrom(data, data.dataPosition(), replySize)
|
||||
transactionReply.setDataPosition(0)
|
||||
transactionReply
|
||||
} else null
|
||||
|
||||
onPostTransact(
|
||||
target,
|
||||
transactionCode,
|
||||
transactionFlags,
|
||||
callingUid,
|
||||
callingPid,
|
||||
transactionData,
|
||||
reply,
|
||||
resultCode,
|
||||
)
|
||||
} finally {
|
||||
transactionData.recycle()
|
||||
transactionReply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun writeResultToReply(result: Result, reply: Parcel) {
|
||||
when (result) {
|
||||
Skip -> reply.writeInt(RESULT_SKIP)
|
||||
@@ -178,4 +196,4 @@ open class BinderInterceptor : Binder() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-37
@@ -15,91 +15,91 @@ import io.github.beakthoven.TrickyStoreOSS.logging.Logger
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
abstract class BaseKeystoreInterceptor : BinderInterceptor() {
|
||||
|
||||
|
||||
protected lateinit var keystore: IBinder
|
||||
protected var triedCount = 0
|
||||
protected var injected = false
|
||||
protected open val maxRetries: Int = 3
|
||||
|
||||
|
||||
protected abstract val serviceName: String
|
||||
protected abstract val injectionCommand: String
|
||||
protected abstract val processName: String
|
||||
|
||||
|
||||
fun tryRunKeystoreInterceptor(): Boolean {
|
||||
Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
|
||||
|
||||
|
||||
val service = getService() ?: return false
|
||||
val backdoor = getBinderBackdoor(service)
|
||||
|
||||
|
||||
return if (backdoor != null) {
|
||||
setupInterceptor(service, backdoor)
|
||||
} else {
|
||||
handleMissingBackdoor()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
|
||||
|
||||
|
||||
protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
|
||||
keystore = service
|
||||
Logger.i("Registering for $serviceName: $keystore")
|
||||
|
||||
|
||||
registerBinderInterceptor(backdoor, service, this)
|
||||
service.linkToDeath(createDeathRecipient(), 0)
|
||||
onInterceptorSetup(service, backdoor)
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private fun handleMissingBackdoor(): Boolean {
|
||||
if (triedCount >= maxRetries) {
|
||||
Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
|
||||
if (!injected) {
|
||||
performInjection()
|
||||
injected = true
|
||||
}
|
||||
|
||||
|
||||
triedCount++
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
protected open fun performInjection() {
|
||||
Logger.i("Attempting to inject into $processName...")
|
||||
|
||||
|
||||
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
|
||||
Logger.d("Injection command: ${command.joinToString(" ")}")
|
||||
|
||||
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
|
||||
|
||||
if (process.waitFor() != 0) {
|
||||
Logger.e("Injection failed! Daemon will exit")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
|
||||
Logger.i("Injection completed successfully")
|
||||
}
|
||||
|
||||
protected open fun createDeathRecipient(): IBinder.DeathRecipient = object : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
Logger.d("$serviceName died, daemon restarting")
|
||||
exitProcess(0)
|
||||
|
||||
protected open fun createDeathRecipient(): IBinder.DeathRecipient =
|
||||
object : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
Logger.d("$serviceName died, daemon restarting")
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
}
|
||||
|
||||
object InterceptorUtils {
|
||||
|
||||
|
||||
fun getTransactCode(clazz: Class<*>, method: String): Int =
|
||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
|
||||
.getInt(null)
|
||||
|
||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }.getInt(null)
|
||||
|
||||
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
||||
val parcel = Parcel.obtain()
|
||||
try {
|
||||
@@ -111,36 +111,43 @@ object InterceptorUtils {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.writeNoException()
|
||||
parcel.writeInt(resultCode)
|
||||
return BinderInterceptor.OverrideReply(0, parcel)
|
||||
}
|
||||
|
||||
fun createByteArrayReply(data: ByteArray, resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
|
||||
|
||||
fun createByteArrayReply(
|
||||
data: ByteArray,
|
||||
resultCode: Int = KeyStore.NO_ERROR,
|
||||
): BinderInterceptor.OverrideReply {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.writeNoException()
|
||||
parcel.writeByteArray(data)
|
||||
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
||||
}
|
||||
|
||||
fun <T : Parcelable?> createTypedObjectReply(obj: T, flags: Int = 0, resultCode: Int = 0): BinderInterceptor.OverrideReply {
|
||||
|
||||
fun <T : Parcelable?> createTypedObjectReply(
|
||||
obj: T,
|
||||
flags: Int = 0,
|
||||
resultCode: Int = 0,
|
||||
): BinderInterceptor.OverrideReply {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.writeNoException()
|
||||
parcel.writeTypedObject(obj, flags)
|
||||
return BinderInterceptor.OverrideReply(resultCode, parcel)
|
||||
}
|
||||
|
||||
|
||||
fun String.extractAlias(): String {
|
||||
return when {
|
||||
contains("_") -> split("_")[1]
|
||||
contains("_") -> split("_")[1]
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Parcel.hasException(): Boolean {
|
||||
return kotlin.runCatching { readException() }.exceptionOrNull() != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-23
@@ -28,23 +28,25 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
|
||||
private val deleteKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
|
||||
|
||||
|
||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||
override val processName = "keystore2"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTrickyStoreOSS.so entry"
|
||||
|
||||
private var teeInterceptor: SecurityLevelInterceptor? = null
|
||||
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
|
||||
|
||||
|
||||
override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
|
||||
setupSecurityLevelInterceptors(service, backdoor)
|
||||
}
|
||||
|
||||
|
||||
private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
|
||||
val ks = IKeystoreService.Stub.asInterface(service)
|
||||
|
||||
val tee = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
|
||||
.getOrNull()
|
||||
|
||||
val tee =
|
||||
kotlin
|
||||
.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
|
||||
.getOrNull()
|
||||
if (tee != null) {
|
||||
Logger.i("Registering for TEE SecurityLevel: $tee")
|
||||
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||
@@ -53,9 +55,9 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
} else {
|
||||
Logger.i("No TEE SecurityLevel found")
|
||||
}
|
||||
|
||||
val strongBox = kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }
|
||||
.getOrNull()
|
||||
|
||||
val strongBox =
|
||||
kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }.getOrNull()
|
||||
if (strongBox != null) {
|
||||
Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
|
||||
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
|
||||
@@ -72,40 +74,62 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
data: Parcel,
|
||||
): Result {
|
||||
if (code == getKeyEntryTransaction) {
|
||||
if (KeyBoxUtils.hasKeyboxes()) {
|
||||
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
|
||||
Logger.d(
|
||||
"intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}"
|
||||
)
|
||||
try {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return Skip
|
||||
if (PkgConfig.needGenerate(callingUid)) {
|
||||
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||
val response =
|
||||
SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||
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)
|
||||
} 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}"
|
||||
)
|
||||
val nullParcel = Parcel.obtain()
|
||||
nullParcel.writeTypedObject(null as KeyEntryResponse?, 0)
|
||||
return OverrideReply(0, nullParcel)
|
||||
}
|
||||
} else if (PkgConfig.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}")
|
||||
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
|
||||
val response =
|
||||
SecurityLevelInterceptor.getKeyResponse(
|
||||
callingUid,
|
||||
descriptor.alias,
|
||||
)
|
||||
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)
|
||||
} 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}"
|
||||
)
|
||||
val nullParcel = Parcel.obtain()
|
||||
nullParcel.writeTypedObject(null as KeyEntryResponse?, 0)
|
||||
return OverrideReply(0, nullParcel)
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
@@ -127,12 +151,14 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int
|
||||
resultCode: Int,
|
||||
): Result {
|
||||
if (target != keystore || reply == null) return Skip
|
||||
if (reply.hasException()) return Skip
|
||||
val p = Parcel.obtain()
|
||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||
Logger.d(
|
||||
"intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}"
|
||||
)
|
||||
|
||||
if (code == deleteKeyTransaction && resultCode == 0) {
|
||||
data.enforceInterface("android.system.keystore2.IKeystoreService")
|
||||
@@ -140,7 +166,9 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
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
|
||||
} else if (code == getKeyEntryTransaction) {
|
||||
@@ -149,7 +177,7 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
|
||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
|
||||
if (response != null) {
|
||||
val chain = CertificateUtils.run { response.getCertificateChain() }
|
||||
if (chain != null) {
|
||||
if (chain != null) {
|
||||
val newChain = CertificateHack.hackCertificateChain(chain, callingUid)
|
||||
response.putCertificateChain(newChain).getOrThrow()
|
||||
Logger.i("Hacked certificate for uid=$callingUid")
|
||||
|
||||
+160
-104
@@ -36,8 +36,7 @@ import java.util.Date
|
||||
|
||||
@SuppressLint("BlockedPrivateApi")
|
||||
object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
private val getTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "get")
|
||||
private val getTransaction = getTransactCode(IKeystoreService.Stub::class.java, "get")
|
||||
private val generateKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
|
||||
private val getKeyCharacteristicsTransaction =
|
||||
@@ -46,7 +45,7 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
|
||||
private val attestKeyTransaction =
|
||||
getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
|
||||
|
||||
|
||||
override val serviceName = "android.security.keystore"
|
||||
override val processName = "keystore"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore` libTrickyStoreOSS.so entry"
|
||||
@@ -64,7 +63,7 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
data: Parcel,
|
||||
): Result {
|
||||
if (KeyBoxUtils.hasKeyboxes()) {
|
||||
if (code == getTransaction) {
|
||||
@@ -76,123 +75,168 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
} else if (PkgConfig.needGenerate(callingUid)) {
|
||||
when (code) {
|
||||
generateKeyTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
|
||||
val check = data.readInt()
|
||||
val kma = KeymasterArguments()
|
||||
val kgp = CertificateGen.KeyGenParameters()
|
||||
if (check == 1) {
|
||||
kma.readFromParcel(data)
|
||||
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||
kgp.keySize = kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||
//kgp.setEcCurveName(kgp.keySize)
|
||||
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
|
||||
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
|
||||
kgp.certificateNotBefore = kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
|
||||
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
|
||||
try {
|
||||
val getArgumentByTag = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getArgumentByTag" }
|
||||
getArgumentByTag.isAccessible = true
|
||||
val rsaArgument = getArgumentByTag.invoke(kma, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
||||
kotlin
|
||||
.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(
|
||||
data.readStrongBinder()
|
||||
)
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
|
||||
val check = data.readInt()
|
||||
val kma = KeymasterArguments()
|
||||
val kgp = CertificateGen.KeyGenParameters()
|
||||
if (check == 1) {
|
||||
kma.readFromParcel(data)
|
||||
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||
kgp.keySize =
|
||||
kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||
// kgp.setEcCurveName(kgp.keySize)
|
||||
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
|
||||
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
|
||||
kgp.certificateNotBefore =
|
||||
kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
|
||||
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
|
||||
try {
|
||||
val getArgumentByTag =
|
||||
KeymasterArguments::class
|
||||
.java
|
||||
.getDeclaredMethods()
|
||||
.first { it.name == "getArgumentByTag" }
|
||||
getArgumentByTag.isAccessible = true
|
||||
val rsaArgument =
|
||||
getArgumentByTag.invoke(
|
||||
kma,
|
||||
KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
|
||||
)
|
||||
|
||||
val getLongTagValue = KeymasterArguments::class.java.getDeclaredMethods().first { it.name == "getLongTagValue" }
|
||||
getLongTagValue.isAccessible = true
|
||||
kgp.rsaPublicExponent = getLongTagValue.invoke(kma, rsaArgument) as BigInteger
|
||||
} catch (ex: Exception) {
|
||||
Logger.e("Read rsaPublicExponent error", ex)
|
||||
val getLongTagValue =
|
||||
KeymasterArguments::class
|
||||
.java
|
||||
.getDeclaredMethods()
|
||||
.first { it.name == "getLongTagValue" }
|
||||
getLongTagValue.isAccessible = true
|
||||
kgp.rsaPublicExponent =
|
||||
getLongTagValue.invoke(kma, rsaArgument)
|
||||
as BigInteger
|
||||
} catch (ex: Exception) {
|
||||
Logger.e("Read rsaPublicExponent error", ex)
|
||||
}
|
||||
}
|
||||
keyArguments[Key(callingUid, alias)] = kgp
|
||||
}
|
||||
keyArguments[Key(callingUid, alias)] = kgp
|
||||
|
||||
val kc = KeyCharacteristics()
|
||||
kc.swEnforced = KeymasterArguments()
|
||||
kc.hwEnforced = kma
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
callback.onFinished(ksr, kc)
|
||||
|
||||
return createSuccessReply()
|
||||
}
|
||||
|
||||
val kc = KeyCharacteristics()
|
||||
kc.swEnforced = KeymasterArguments()
|
||||
kc.hwEnforced = kma
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
callback.onFinished(ksr, kc)
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("generateKeyTransaction error", it)
|
||||
}
|
||||
.onFailure { Logger.e("generateKeyTransaction error", it) }
|
||||
}
|
||||
|
||||
getKeyCharacteristicsTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("getKeyCharacteristicsTransaction uid $callingUid alias $alias")
|
||||
val kc = KeyCharacteristics()
|
||||
val kma = KeymasterArguments()
|
||||
kma.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, keyArguments[Key(callingUid, alias)]!!.algorithm)
|
||||
kc.swEnforced = KeymasterArguments()
|
||||
kc.hwEnforced = kma
|
||||
kotlin
|
||||
.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(
|
||||
data.readStrongBinder()
|
||||
)
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i(
|
||||
"getKeyCharacteristicsTransaction uid $callingUid alias $alias"
|
||||
)
|
||||
val kc = KeyCharacteristics()
|
||||
val kma = KeymasterArguments()
|
||||
kma.addEnum(
|
||||
KeymasterDefs.KM_TAG_ALGORITHM,
|
||||
keyArguments[Key(callingUid, alias)]!!.algorithm,
|
||||
)
|
||||
kc.swEnforced = KeymasterArguments()
|
||||
kc.hwEnforced = kma
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
callback.onFinished(ksr, kc)
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
callback.onFinished(ksr, kc)
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("getKeyCharacteristicsTransaction error", it)
|
||||
}
|
||||
return createSuccessReply()
|
||||
}
|
||||
.onFailure { Logger.e("getKeyCharacteristicsTransaction error", it) }
|
||||
}
|
||||
|
||||
exportKeyTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
|
||||
val kp = CertificateGen.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
|
||||
keyPairs[Key(callingUid, alias)] = kp!!
|
||||
kotlin
|
||||
.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreExportKeyCallback.Stub.asInterface(
|
||||
data.readStrongBinder()
|
||||
)
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
|
||||
val kp =
|
||||
CertificateGen.generateKeyPair(
|
||||
keyArguments[Key(callingUid, alias)]!!
|
||||
)
|
||||
keyPairs[Key(callingUid, alias)] = kp!!
|
||||
|
||||
val erP = Parcel.obtain()
|
||||
erP.writeInt(KeyStore.NO_ERROR)
|
||||
erP.writeByteArray(kp.public.encoded)
|
||||
erP.setDataPosition(0)
|
||||
val er = ExportResult.CREATOR.createFromParcel(erP)
|
||||
erP.recycle()
|
||||
val erP = Parcel.obtain()
|
||||
erP.writeInt(KeyStore.NO_ERROR)
|
||||
erP.writeByteArray(kp.public.encoded)
|
||||
erP.setDataPosition(0)
|
||||
val er = ExportResult.CREATOR.createFromParcel(erP)
|
||||
erP.recycle()
|
||||
|
||||
callback.onFinished(er)
|
||||
callback.onFinished(er)
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("exportKeyTransaction error", it)
|
||||
}
|
||||
return createSuccessReply()
|
||||
}
|
||||
.onFailure { Logger.e("exportKeyTransaction error", it) }
|
||||
}
|
||||
|
||||
attestKeyTransaction -> {
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback = IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("attestKeyTransaction uid $callingUid alias $alias")
|
||||
val check = data.readInt()
|
||||
val kma = KeymasterArguments()
|
||||
if (check == 1) {
|
||||
kma.readFromParcel(data)
|
||||
val attestationChallenge = kma.getBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, ByteArray(0))
|
||||
kotlin
|
||||
.runCatching {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreCertificateChainCallback.Stub.asInterface(
|
||||
data.readStrongBinder()
|
||||
)
|
||||
val alias = data.readString()!!.extractAlias()
|
||||
Logger.i("attestKeyTransaction uid $callingUid alias $alias")
|
||||
val check = data.readInt()
|
||||
val kma = KeymasterArguments()
|
||||
if (check == 1) {
|
||||
kma.readFromParcel(data)
|
||||
val attestationChallenge =
|
||||
kma.getBytes(
|
||||
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
|
||||
ByteArray(0),
|
||||
)
|
||||
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
val ksr = createSuccessKeystoreResponse()
|
||||
|
||||
val key = Key(callingUid, alias)
|
||||
val ka = keyArguments[key]!!
|
||||
ka.attestationChallenge = attestationChallenge
|
||||
val chain = CertificateGen.generateChain(callingUid, ka, keyPairs[key]!!)
|
||||
val key = Key(callingUid, alias)
|
||||
val ka = keyArguments[key]!!
|
||||
ka.attestationChallenge = attestationChallenge
|
||||
val chain =
|
||||
CertificateGen.generateChain(
|
||||
callingUid,
|
||||
ka,
|
||||
keyPairs[key]!!,
|
||||
)
|
||||
|
||||
val kcc = KeymasterCertificateChain(chain)
|
||||
callback.onFinished(ksr, kcc)
|
||||
val kcc = KeymasterCertificateChain(chain)
|
||||
callback.onFinished(ksr, kcc)
|
||||
}
|
||||
|
||||
return createSuccessReply()
|
||||
}
|
||||
|
||||
return createSuccessReply()
|
||||
}.onFailure {
|
||||
Logger.e("attestKeyTransaction error", it)
|
||||
}
|
||||
.onFailure { Logger.e("attestKeyTransaction error", it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,24 +252,36 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int
|
||||
resultCode: Int,
|
||||
): Result {
|
||||
if (target != keystore || code != getTransaction || reply == null) return Skip
|
||||
if (reply.hasException()) return Skip
|
||||
val p = Parcel.obtain()
|
||||
Logger.d("intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}")
|
||||
Logger.d(
|
||||
"intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}"
|
||||
)
|
||||
try {
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val alias = data.readString() ?: ""
|
||||
var response = reply.createByteArray()
|
||||
when {
|
||||
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
|
||||
response = CertificateHack.hackUserCertificate(response!!, alias.extractAlias(), callingUid)
|
||||
response =
|
||||
CertificateHack.hackUserCertificate(
|
||||
response!!,
|
||||
alias.extractAlias(),
|
||||
callingUid,
|
||||
)
|
||||
Logger.i("Hacked leaf certificate for uid=$callingUid")
|
||||
return createByteArrayReply(response)
|
||||
}
|
||||
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
|
||||
response = CertificateHack.hackCACertificateChain(response!!, alias.extractAlias(), callingUid)
|
||||
response =
|
||||
CertificateHack.hackCACertificateChain(
|
||||
response!!,
|
||||
alias.extractAlias(),
|
||||
callingUid,
|
||||
)
|
||||
Logger.i("Hacked CA certificate chain for uid=$callingUid")
|
||||
return createByteArrayReply(response)
|
||||
}
|
||||
@@ -237,4 +293,4 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+71
-44
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class SecurityLevelInterceptor(
|
||||
private val original: IKeystoreSecurityLevel,
|
||||
private val level: Int
|
||||
private val level: Int,
|
||||
) : BinderInterceptor() {
|
||||
companion object {
|
||||
private val generateKeyTransaction =
|
||||
@@ -37,14 +37,11 @@ class SecurityLevelInterceptor(
|
||||
private val createOperationTransaction =
|
||||
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
|
||||
|
||||
@Keep
|
||||
val keys = ConcurrentHashMap<Key, Info>()
|
||||
@Keep val keys = ConcurrentHashMap<Key, Info>()
|
||||
|
||||
@Keep
|
||||
val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
|
||||
@Keep val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
|
||||
|
||||
@Keep
|
||||
val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
|
||||
@Keep val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
|
||||
|
||||
@Keep
|
||||
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
|
||||
@@ -60,6 +57,7 @@ class SecurityLevelInterceptor(
|
||||
}
|
||||
|
||||
data class Key(val uid: Int, val alias: String)
|
||||
|
||||
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
|
||||
|
||||
override fun onPreTransact(
|
||||
@@ -68,51 +66,80 @@ class SecurityLevelInterceptor(
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel
|
||||
data: Parcel,
|
||||
): Result {
|
||||
if (code == generateKeyTransaction) {
|
||||
Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
|
||||
kotlin.runCatching {
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val aFlags = data.readInt()
|
||||
val entropy = data.createByteArray()
|
||||
val kgp = CertificateGen.KeyGenParameters(params)
|
||||
if (PkgConfig.needGenerate(callingUid)) {
|
||||
val pair = CertificateGen.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp, level)
|
||||
?: return@runCatching
|
||||
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
||||
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||
val p = Parcel.obtain()
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response.metadata, 0)
|
||||
return OverrideReply(0, p)
|
||||
} else if (PkgConfig.needHack(callingUid)) {
|
||||
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
|
||||
Logger.i("Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}")
|
||||
val pair = CertificateGen.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp, level)
|
||||
?: return@runCatching
|
||||
keyPairs[Key(callingUid, keyDescriptor.alias)] = Pair(pair.first, pair.second)
|
||||
val response = buildResponse(pair.second, kgp, attestationKeyDescriptor ?: keyDescriptor)
|
||||
kotlin
|
||||
.runCatching {
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
|
||||
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val aFlags = data.readInt()
|
||||
val entropy = data.createByteArray()
|
||||
val kgp = CertificateGen.KeyGenParameters(params)
|
||||
if (PkgConfig.needGenerate(callingUid)) {
|
||||
val pair =
|
||||
CertificateGen.generateKeyPair(
|
||||
callingUid,
|
||||
keyDescriptor,
|
||||
attestationKeyDescriptor,
|
||||
kgp,
|
||||
level,
|
||||
) ?: return@runCatching
|
||||
keyPairs[Key(callingUid, keyDescriptor.alias)] =
|
||||
Pair(pair.first, pair.second)
|
||||
val response =
|
||||
buildResponse(
|
||||
pair.second,
|
||||
kgp,
|
||||
attestationKeyDescriptor ?: keyDescriptor,
|
||||
)
|
||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||
SecurityLevelInterceptor.skipLeafHacks[Key(callingUid, keyDescriptor.alias)] = true
|
||||
val p = Parcel.obtain()
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response.metadata, 0)
|
||||
return OverrideReply(0, p)
|
||||
} else {
|
||||
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
|
||||
Logger.i("Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}")
|
||||
return Skip
|
||||
} else if (PkgConfig.needHack(callingUid)) {
|
||||
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
|
||||
Logger.i(
|
||||
"Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}"
|
||||
)
|
||||
val pair =
|
||||
CertificateGen.generateKeyPair(
|
||||
callingUid,
|
||||
keyDescriptor,
|
||||
attestationKeyDescriptor,
|
||||
kgp,
|
||||
level,
|
||||
) ?: return@runCatching
|
||||
keyPairs[Key(callingUid, keyDescriptor.alias)] =
|
||||
Pair(pair.first, pair.second)
|
||||
val response =
|
||||
buildResponse(
|
||||
pair.second,
|
||||
kgp,
|
||||
attestationKeyDescriptor ?: keyDescriptor,
|
||||
)
|
||||
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
|
||||
SecurityLevelInterceptor.skipLeafHacks[
|
||||
Key(callingUid, keyDescriptor.alias)] = true
|
||||
val p = Parcel.obtain()
|
||||
p.writeNoException()
|
||||
p.writeTypedObject(response.metadata, 0)
|
||||
return OverrideReply(0, p)
|
||||
} else {
|
||||
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
|
||||
Logger.i(
|
||||
"Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}"
|
||||
)
|
||||
return Skip
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Logger.e("parse key gen request", it)
|
||||
}
|
||||
.onFailure { Logger.e("parse key gen request", it) }
|
||||
}
|
||||
return Skip
|
||||
}
|
||||
@@ -120,7 +147,7 @@ class SecurityLevelInterceptor(
|
||||
private fun buildResponse(
|
||||
chain: List<Certificate>,
|
||||
params: CertificateGen.KeyGenParameters,
|
||||
descriptor: KeyDescriptor
|
||||
descriptor: KeyDescriptor,
|
||||
): KeyEntryResponse {
|
||||
val response = KeyEntryResponse()
|
||||
val metadata = KeyMetadata()
|
||||
@@ -177,4 +204,4 @@ class SecurityLevelInterceptor(
|
||||
response.iSecurityLevel = original
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,56 +9,65 @@ import android.util.Log
|
||||
|
||||
object Logger {
|
||||
const val TAG = "TrickyStoreOSS"
|
||||
|
||||
|
||||
sealed class LogLevel(val priority: Int) {
|
||||
object Debug : LogLevel(Log.DEBUG)
|
||||
|
||||
object Info : LogLevel(Log.INFO)
|
||||
|
||||
object Warning : LogLevel(Log.WARN)
|
||||
|
||||
object Error : LogLevel(Log.ERROR)
|
||||
|
||||
object Verbose : LogLevel(Log.VERBOSE)
|
||||
}
|
||||
|
||||
|
||||
fun d(message: String) {
|
||||
Log.d(TAG, message)
|
||||
}
|
||||
|
||||
|
||||
fun e(message: String) {
|
||||
Log.e(TAG, message)
|
||||
}
|
||||
|
||||
|
||||
fun e(message: String, throwable: Throwable) {
|
||||
Log.e(TAG, "fatal: $message", throwable)
|
||||
}
|
||||
|
||||
|
||||
fun i(message: String) {
|
||||
Log.i(TAG, message)
|
||||
}
|
||||
|
||||
|
||||
fun w(message: String) {
|
||||
Log.w(TAG, message)
|
||||
}
|
||||
|
||||
|
||||
fun w(message: String, throwable: Throwable) {
|
||||
Log.w(TAG, message, throwable)
|
||||
}
|
||||
|
||||
|
||||
fun v(message: String) {
|
||||
Log.v(TAG, message)
|
||||
}
|
||||
|
||||
|
||||
fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
|
||||
when (level) {
|
||||
is LogLevel.Debug -> if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
|
||||
is LogLevel.Info -> if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
|
||||
is LogLevel.Warning -> if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
|
||||
is LogLevel.Error -> if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
|
||||
is LogLevel.Verbose -> if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
|
||||
is LogLevel.Debug ->
|
||||
if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
|
||||
is LogLevel.Info ->
|
||||
if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
|
||||
is LogLevel.Warning ->
|
||||
if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
|
||||
is LogLevel.Error ->
|
||||
if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
|
||||
is LogLevel.Verbose ->
|
||||
if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
|
||||
if (condition && Log.isLoggable(TAG, level.priority)) {
|
||||
log(level, messageProvider())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -3,9 +3,20 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
}
|
||||
alias(libs.plugins.ktfmt)
|
||||
}
|
||||
|
||||
tasks.register<KtfmtFormatTask>("format") {
|
||||
source = project.fileTree(rootDir)
|
||||
include("*.gradle.kts", "app/*.gradle.kts")
|
||||
dependsOn(":app:ktfmtFormat")
|
||||
}
|
||||
|
||||
ktfmt { kotlinLangStyle() }
|
||||
|
||||
@@ -4,6 +4,7 @@ annotation = "1.9.1"
|
||||
jdk18on = "1.81"
|
||||
kotlin = "2.2.10"
|
||||
libcxx = "28.1.13356709"
|
||||
ktfmt = "0.25.0"
|
||||
|
||||
[libraries]
|
||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||
@@ -14,4 +15,5 @@ org-lsposed-libcxx-libcxx = { module = "org.lsposed.libcxx:libcxx", version.ref
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
@@ -25,4 +26,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
rootProject.name = "Tricky Store OSS"
|
||||
|
||||
include(":app", ":stub")
|
||||
|
||||
Reference in New Issue
Block a user