app: java: Restructure and tidy up the code

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
This commit is contained in:
Dakkshesh
2025-08-30 16:02:34 +05:30
parent 23774e64a9
commit 808c3a5ff9
15 changed files with 1240 additions and 1237 deletions
+1 -1
View File
@@ -24,7 +24,7 @@
public static void main(java.lang.String[]);
}
-assumenosideeffects class io.github.beakthoven.TrickyStoreOSS.core.logging.Logger {
-assumenosideeffects class io.github.beakthoven.TrickyStoreOSS.logging.Logger {
public static void d(java.lang.String);
public static void dd(java.lang.String);
public static void v(java.lang.String);
@@ -10,227 +10,223 @@ import android.content.pm.PackageManager
import android.os.Build
import android.os.ServiceManager
import android.os.SystemProperties
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.config.CustomPatchLevel
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import 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 org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import java.security.MessageDigest
import java.util.concurrent.ThreadLocalRandom
fun getTransactCode(clazz: Class<*>, method: String): Int =
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
.getInt(null)
object AndroidUtils {
// cache attest data to avoid running attestation multiple times
private val cachedAttestData: AttestationData? by lazy {
getAttestData() // from CertHacker
}
val bootKey: ByteArray by lazy {
randomBytes()
}
fun setupBootHash() {
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)
}
}
@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
}
private fun getBootHashFromAttestation(): ByteArray? {
return try {
cachedAttestData?.verifiedBootHash
} catch (e: Exception) {
Logger.e("Failed to get boot hash from attestation: ${e.message}")
null
}
}
private fun setBootHashProp(bytes: ByteArray) {
val hex = bytes.toHex()
try {
Logger.d("Setting ro.boot.vbmeta.digest to: $hex")
SystemProperties.set("ro.boot.vbmeta.digest", hex)
} catch (e: Exception) {
Logger.e("Exception setting vbmeta digest: ${e.message}")
}
}
private fun randomBytes(): ByteArray = ByteArray(32).also {
ThreadLocalRandom.current().nextBytes(it)
}
val patchLevel: Int
get() = getCustomPatchLevel("system", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val patchLevelLong: Int
get() = getCustomPatchLevel("system", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val vendorPatchLevel: Int
get() = getCustomPatchLevel("vendor", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val vendorPatchLevelLong: Int
get() = getCustomPatchLevel("vendor", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val bootPatchLevel: Int
get() = getCustomPatchLevel("boot", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val bootPatchLevelLong: Int
get() = getCustomPatchLevel("boot", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
private val customPatchLevel: CustomPatchLevel?
get() = Config._customPatchLevel
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
val config = customPatchLevel ?: return null
val value = when (component) {
"system" -> config.system ?: config.all
"vendor" -> config.vendor ?: config.all
"boot" -> config.boot ?: config.all
else -> config.all
} ?: return null
when {
value.equals("no", ignoreCase = true) -> return null
value.equals("prop", ignoreCase = true) -> return null
val bootKey: ByteArray by lazy {
randomBytes()
}
return parsePatchLevelValue(value, component, isLong)
}
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
val normalized = value.replace("-", "")
return try {
when (normalized.length) {
8 -> {
val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt()
val day = normalized.substring(6, 8).toInt()
if (isLong) year * 10000 + month * 100 + day
else year * 100 + month
}
6 -> {
val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt()
if (isLong) year * 10000 + month * 100
else year * 100 + month
}
else -> {
Logger.e("Invalid patch level length for $component: $normalized")
null
}
fun setupBootHash() {
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)
}
} catch (e: NumberFormatException) {
Logger.e("Patch level parse error for $component=$value", e)
null
}
}
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
)
val attestVersion: Int
get() = cachedAttestData?.attestVersion ?: attestVersionMap[Build.VERSION.SDK_INT] ?: 400
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)
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)
@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
}
packages.list
.map { it.packageName to it.longVersionCode }
.sortedBy { it.first }
}.getOrElse {
Logger.e("Failed to get APEX package information")
emptyList()
return if (digest.length == 64) digest.hexToByteArray() else null
}
}
val moduleHash: ByteArray by lazy {
runCatching {
val encodables = apexInfos.flatMap { (packageName, versionCode) ->
listOf(
DEROctetString(packageName.toByteArray()),
ASN1Integer(versionCode)
)
private fun getBootHashFromAttestation(): ByteArray? {
return try {
CachedAttestData?.verifiedBootHash
} catch (e: Exception) {
Logger.e("Failed to get boot hash from attestation: ${e.message}")
null
}
val sequence = DERSequence(encodables.toTypedArray())
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
}.getOrElse {
Logger.e("Failed to compute module hash", it)
ByteArray(32)
}
private fun setBootHashProp(bytes: ByteArray) {
val hex = bytes.toHex()
try {
Logger.d("Setting ro.boot.vbmeta.digest to: $hex")
SystemProperties.set("ro.boot.vbmeta.digest", hex)
} catch (e: Exception) {
Logger.e("Exception setting vbmeta digest: ${e.message}")
}
}
private fun randomBytes(): ByteArray = ByteArray(32).also {
ThreadLocalRandom.current().nextBytes(it)
}
val patchLevel: Int
get() = getCustomPatchLevel("system", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val patchLevelLong: Int
get() = getCustomPatchLevel("system", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val vendorPatchLevel: Int
get() = getCustomPatchLevel("vendor", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val vendorPatchLevelLong: Int
get() = getCustomPatchLevel("vendor", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val bootPatchLevel: Int
get() = getCustomPatchLevel("boot", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val bootPatchLevelLong: Int
get() = getCustomPatchLevel("boot", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
private val customPatchLevel: CustomPatchLevel?
get() = 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
when {
value.equals("no", ignoreCase = true) -> return null
value.equals("prop", ignoreCase = true) -> return null
}
return parsePatchLevelValue(value, component, isLong)
}
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
val normalized = value.replace("-", "")
return try {
when (normalized.length) {
8 -> {
val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt()
val day = normalized.substring(6, 8).toInt()
if (isLong) year * 10000 + month * 100 + day
else year * 100 + month
}
6 -> {
val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt()
if (isLong) year * 10000 + month * 100
else year * 100 + month
}
else -> {
Logger.e("Invalid patch level length for $component: $normalized")
null
}
}
} catch (e: NumberFormatException) {
Logger.e("Patch level parse error for $component=$value", e)
null
}
}
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
)
val attestVersion: Int
get() = CachedAttestData?.attestVersion ?: attestVersionMap[Build.VERSION.SDK_INT] ?: 400
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)
val apexInfos: List<Pair<String, Long>> by lazy {
runCatching {
val packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
val packages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
} else {
@Suppress("DEPRECATION")
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
}
packages.list
.map { it.packageName to it.longVersionCode }
.sortedBy { it.first }
}.getOrElse {
Logger.e("Failed to get APEX package information")
emptyList()
}
}
val moduleHash: ByteArray by lazy {
runCatching {
val encodables = apexInfos.flatMap { (packageName, versionCode) ->
listOf(
DEROctetString(packageName.toByteArray()),
ASN1Integer(versionCode)
)
}
val sequence = DERSequence(encodables.toTypedArray())
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
}.getOrElse {
Logger.e("Failed to compute module hash", it)
ByteArray(32)
}
}
}
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
@@ -0,0 +1,153 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1OctetString
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")
object AttestUtils {
data class AttestationData(
val verifiedBootHash: ByteArray?,
val attestVersion: Int?,
val keymasterVersion: Int?,
val osVersion: Int?,
)
val TEEStatus: Boolean by lazy { isTEEWorking() }
val CachedAttestData: AttestationData? by lazy { getAttestData()}
private val keygen_alias = "TrickyStoreOSS_attest"
private fun isTEEWorking(): Boolean {
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
android.app.ActivityThread.initializeMainlineModules()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
android.security.keystore2.AndroidKeyStoreProvider.install()
} else {
android.security.keystore.AndroidKeyStoreProvider.install()
}
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val challenge = ByteArray(16).apply {
SecureRandom().nextBytes(this)
}
val parameterSpec = KeyGenParameterSpec.Builder(
keygen_alias,
KeyProperties.PURPOSE_SIGN
)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.setIsStrongBoxBacked(false)
.build()
keyPairGenerator.initialize(parameterSpec)
keyPairGenerator.generateKeyPair()
Logger.d("TEE check: successful")
// keyStore.deleteEntry(keygen_alias)
true
} catch (e: Exception) {
Logger.w("TEE check failure: ${e.message}")
false
}
}
private fun getAttestCert(): X509Certificate? {
return if (TEEStatus) {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val certChain = keyStore.getCertificateChain(keygen_alias)
if (certChain == null || certChain.isEmpty()) {
null
} else {
keyStore.deleteEntry(keygen_alias)
certChain[0] as X509Certificate
}
} else {
null
}
}
private fun getAttestData(): AttestationData? {
val leaf: X509Certificate = getAttestCert() ?: return null
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 keyDescriptionSeq = ASN1Sequence.getInstance(ext.extnValue.octets)
val encodables = keyDescriptionSeq.toArray()
val attestVersion = ASN1Integer.getInstance(encodables[0]).value.intValueExact()
val keymasterVersion = ASN1Integer.getInstance(encodables[2]).value.intValueExact()
var attestVerifiedBootHash: ByteArray? = null
var attestOSVersion: Int? = null
val teeEnforced = ASN1Sequence.getInstance(encodables[7])
teeEnforced.forEach { element ->
val tagged = element as ASN1TaggedObject
when (tagged.tagNo) {
704 -> { // Parse Root of Trust
val rootOfTrustSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
if (rootOfTrustSeq.size() >= 4) {
attestVerifiedBootHash = ASN1OctetString.getInstance(rootOfTrustSeq.getObjectAt(3)).octets
}
}
705 -> { // Parse OS Version
attestOSVersion = ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive()).value.intValueExact()
}
}
}
Logger.i("Extracted attestationVersion: $attestVersion")
Logger.i("Extracted keymasterVersion: $keymasterVersion")
Logger.i("Extracted verifiedBootHash: ${attestVerifiedBootHash?.toHex() ?: 0}")
Logger.i("Extracted osVersion: $attestOSVersion")
AttestationData(
verifiedBootHash = attestVerifiedBootHash,
attestVersion = attestVersion,
keymasterVersion = keymasterVersion,
osVersion = attestOSVersion
)
} catch (e: Exception) {
Logger.e("Failed to parse attestation data", e)
null
}
}
}
@@ -0,0 +1,471 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.content.pm.PackageManager
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.EcCurve
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.Tag
import android.os.Build
import android.security.keystore.KeyProperties
import android.system.keystore2.KeyDescriptor
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 org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERSet
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
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>
)
private data class Digest(val digest: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Digest
return digest.contentEquals(other.digest)
}
override fun hashCode(): Int = digest.contentHashCode()
}
data class KeyGenParameters(
var keySize: Int = 0,
var algorithm: Int = 0,
var certificateSerial: BigInteger? = null,
var certificateNotBefore: Date? = null,
var certificateNotAfter: Date? = null,
var certificateSubject: X500Name? = null,
var rsaPublicExponent: BigInteger? = null,
var ecCurve: Int = 0,
var ecCurveName: String? = null,
var purpose: MutableList<Int> = mutableListOf(),
var digest: MutableList<Int> = mutableListOf(),
var attestationChallenge: ByteArray? = null,
var brand: ByteArray? = null,
var device: ByteArray? = null,
var product: ByteArray? = null,
var manufacturer: ByteArray? = null,
var model: ByteArray? = null,
var imei1: ByteArray? = null,
var imei2: ByteArray? = null,
var meid: ByteArray? = null,
var serialno: ByteArray? = null
) {
constructor(params: Array<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.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
Tag.EC_CURVE -> {
ecCurve = value.ecCurve
ecCurveName = getEcCurveName(ecCurve)
}
Tag.PURPOSE -> purpose.add(value.keyPurpose)
Tag.DIGEST -> digest.add(value.digest)
Tag.ATTESTATION_CHALLENGE -> attestationChallenge = value.blob
Tag.ATTESTATION_ID_BRAND -> brand = value.blob
Tag.ATTESTATION_ID_DEVICE -> device = value.blob
Tag.ATTESTATION_ID_PRODUCT -> product = value.blob
Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = value.blob
Tag.ATTESTATION_ID_MODEL -> model = value.blob
Tag.ATTESTATION_ID_IMEI -> imei1 = value.blob
Tag.ATTESTATION_ID_SECOND_IMEI -> imei2 = value.blob
Tag.ATTESTATION_ID_MEID -> meid = value.blob
}
}
// Fallback: if no EC curve tag but we know key size
if (ecCurveName == null && keySize != 0) {
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")
}
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)
}
}
fun generateChain(uid: Int, params: KeyGenParameters, keyPair: KeyPair, securityLevel: Int = 1): List<ByteArray>? = runCatching {
val keybox = getKeyboxForAlgorithm(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 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()
}.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(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
}
}
private fun getKeyboxForAlgorithm(algorithm: Int): KeyBox? {
val algorithmName = mapAlgorithmToName(algorithm) ?: return null
return KeyBoxUtils.keyboxes[algorithmName]
}
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
Pair(keyInfo.first, issuer)
} else {
Logger.e("Attestation key info not found, falling back to default keybox")
null
}
}
private fun buildCertificate(
keyPair: KeyPair,
keybox: KeyBox,
params: KeyGenParameters,
issuer: X500Name,
uid: Int,
securityLevel: Int = 1,
signingKeyPair: KeyPair = keybox.keyPair
): Certificate {
val builder = JcaX509v3CertificateBuilder(
issuer,
params.certificateSerial ?: BigInteger.ONE,
params.certificateNotBefore ?: Date(),
params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
keyPair.public
)
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
builder.addExtension(buildAttestExtension(params, uid, securityLevel))
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 {
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 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)
val bootPatchLevel = ASN1Integer(AndroidUtils.bootPatchLevelLong.toLong())
val vendorPatchLevel = ASN1Integer(AndroidUtils.vendorPatchLevelLong.toLong())
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),
)
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))) }
if (AndroidUtils.attestVersion >= 300) {
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)
)
return Extension(
ATTESTATION_OID,
false,
buildKeyDescriptionOctet(teeEnforcedObjects.toTypedArray(), softwareEnforcedObjects, params, securityLevel)
)
} catch (t: Throwable) {
Logger.e("Failed to create attestation extension", t)
throw t
}
}
private fun buildKeyDescriptionOctet(
teeEnforcedEncodables: Array<ASN1Encodable>,
softwareEnforcedEncodables: Array<ASN1Encodable>,
params: KeyGenParameters,
securityLevel: Int = 1
): ASN1OctetString {
val attestationVersion = ASN1Integer(AndroidUtils.attestVersion.toLong())
val attestationSecurityLevel = ASN1Enumerated(securityLevel)
val keymasterVersion = ASN1Integer(AndroidUtils.keymasterVersion.toLong())
val keymasterSecurityLevel = ASN1Enumerated(securityLevel)
val attestationChallenge = DEROctetString(params.attestationChallenge ?: ByteArray(0))
val uniqueId = DEROctetString(ByteArray(0))
val softwareEnforced = DERSequence(softwareEnforcedEncodables)
val teeEnforced = DERSequence(teeEnforcedEncodables)
val keyDescriptionEncodables = arrayOf(
attestationVersion,
attestationSecurityLevel,
keymasterVersion,
keymasterSecurityLevel,
attestationChallenge,
uniqueId,
softwareEnforced,
teeEnforced
)
val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
return DEROctetString(keyDescriptionSeq.encoded)
}
@Throws(Throwable::class)
private fun createApplicationId(uid: Int): DEROctetString {
val pm = PkgConfig.getPm() ?: throw IllegalStateException("PackageManager not found!")
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)
}
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 signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
val applicationIdArray = arrayOf(
DERSet(packageInfoArray),
DERSet(signaturesArray)
)
return DEROctetString(DERSequence(applicationIdArray).encoded)
}
}
@@ -0,0 +1,241 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1TaggedObject
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x509.Extension
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 {
private val certificateFactory: CertificateFactory by lazy {
try {
CertificateFactory.getInstance("X.509")
} catch (t: Throwable) {
Logger.e("Failed to initialize certificate factory", t)
throw RuntimeException("Cannot initialize certificate factory", t)
}
}
data class KeyIdentifier(
val alias: String,
val uid: Int
)
val leafAlgorithms = ConcurrentHashMap<KeyIdentifier, String>()
fun clearLeafAlgorithms() {
leafAlgorithms.clear()
}
fun hackCertificateChain(certificateChain: Array<Certificate>?): 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 leafHolder = X509CertificateHolder(leaf.encoded)
val extension = leafHolder.getExtension(ATTESTATION_OID)
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
val encodables = sequence.toArray()
val teeEnforced = encodables[7] as ASN1Sequence
val vector = ASN1EncodableVector()
var rootOfTrust: ASN1Encodable? = null
teeEnforced.forEach { element ->
val taggedObject = element as ASN1TaggedObject
if (taggedObject.tagNo == 704) {
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
vector.add(taggedObject)
}
}
val keybox = KeyBoxUtils.keyboxes[leaf.publicKey.algorithm]
?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
val certificates = LinkedList(keybox.certificates)
val builder = X509v3CertificateBuilder(
X509CertificateHolder(certificates[0].encoded).subject,
leafHolder.serialNumber,
leafHolder.notBefore,
leafHolder.notAfter,
leafHolder.subject,
leafHolder.subjectPublicKeyInfo
)
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
val hackedExtension = 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.toTypedArray()
} catch (t: Throwable) {
Logger.e("Failed to hack certificate chain", 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 keybox = KeyBoxUtils.keyboxes[algorithm]
?: throw UnsupportedOperationException("Unsupported algorithm: $algorithm")
CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
} catch (t: Throwable) {
Logger.e("Failed to hack CA certificate chain", t)
caList
}
}
fun 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 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) {
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
vector.add(taggedObject)
}
}
val keybox = KeyBoxUtils.keyboxes[leaf.publicKey.algorithm]
?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
val builder = X509v3CertificateBuilder(
X509CertificateHolder(keybox.certificates[0].encoded).subject,
leafHolder.serialNumber,
leafHolder.notBefore,
leafHolder.notAfter,
leafHolder.subject,
leafHolder.subjectPublicKeyInfo
)
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
val hackedExtension = hackAttestExtension(rootOfTrust, vector, encodables)
builder.addExtension(hackedExtension)
leafHolder.extensions.extensionOIDs.forEach { oid ->
if (oid.id != ATTESTATION_OID.id) {
builder.addExtension(leafHolder.getExtension(oid))
}
}
JcaX509CertificateConverter().getCertificate(builder.build(signer)).encoded
} catch (t: Throwable) {
Logger.e("Failed to hack user certificate", t)
certificate
}
}
private fun hackAttestExtension(
originalRootOfTrust: ASN1Encodable?,
vector: ASN1EncodableVector,
originalEncodables: Array<ASN1Encodable>
): Extension {
val verifiedBootKey = AndroidUtils.bootKey
var verifiedBootHash: ByteArray? = null
try {
if (originalRootOfTrust is ASN1Sequence) {
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 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, 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)
}
}
@@ -1,984 +0,0 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.content.pm.PackageManager
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.EcCurve
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.Tag
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.system.keystore2.KeyDescriptor
import android.util.Pair
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor
import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1TaggedObject
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERSet
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.ByteArrayInputStream
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.MessageDigest
import java.security.SecureRandom
import java.security.Security
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
import java.util.Date
import java.util.LinkedList
import java.util.concurrent.ConcurrentHashMap
import javax.security.auth.x500.X500Principal
object CertificateHacker {
private val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
private val certificateFactory: CertificateFactory by lazy {
try {
CertificateFactory.getInstance("X.509")
} catch (t: Throwable) {
Logger.e("Failed to initialize certificate factory", t)
throw RuntimeException("Cannot initialize certificate factory", t)
}
}
data class KeyBox(
val pemKeyPair: PEMKeyPair,
val keyPair: KeyPair,
val certificates: List<Certificate>
)
data class KeyIdentifier(
val alias: String,
val uid: Int
)
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>()
}
sealed class HackResult<out T> {
data class Success<T>(val data: T) : HackResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : HackResult<Nothing>()
}
data class KeyGenParameters(
var keySize: Int = 0,
var algorithm: Int = 0,
var certificateSerial: BigInteger? = null,
var certificateNotBefore: Date? = null,
var certificateNotAfter: Date? = null,
var certificateSubject: X500Name? = null,
var rsaPublicExponent: BigInteger? = null,
var ecCurve: Int = 0,
var ecCurveName: String? = null,
var purpose: MutableList<Int> = mutableListOf(),
var digest: MutableList<Int> = mutableListOf(),
var attestationChallenge: ByteArray? = null,
var brand: ByteArray? = null,
var device: ByteArray? = null,
var product: ByteArray? = null,
var manufacturer: ByteArray? = null,
var model: ByteArray? = null,
var imei1: ByteArray? = null,
var imei2: ByteArray? = null,
var meid: ByteArray? = null,
var serialno: ByteArray? = null
) {
constructor(params: Array<KeyParameter>) : this() {
parseKeyParameters(params)
}
private fun parseKeyParameters(params: Array<KeyParameter>) {
params.forEach { param ->
Logger.d("Processing key parameter: ${param.tag}")
val value = param.value
when (param.tag) {
Tag.KEY_SIZE -> keySize = value.integer
Tag.ALGORITHM -> algorithm = value.algorithm
Tag.CERTIFICATE_SERIAL -> certificateSerial = BigInteger(value.blob)
Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = Date(value.dateTime)
Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = Date(value.dateTime)
Tag.CERTIFICATE_SUBJECT -> certificateSubject = X500Name(X500Principal(value.blob).name)
Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
Tag.EC_CURVE -> {
ecCurve = value.ecCurve
ecCurveName = getEcCurveName(ecCurve)
}
Tag.PURPOSE -> purpose.add(value.keyPurpose)
Tag.DIGEST -> digest.add(value.digest)
Tag.ATTESTATION_CHALLENGE -> attestationChallenge = value.blob
Tag.ATTESTATION_ID_BRAND -> brand = value.blob
Tag.ATTESTATION_ID_DEVICE -> device = value.blob
Tag.ATTESTATION_ID_PRODUCT -> product = value.blob
Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = value.blob
Tag.ATTESTATION_ID_MODEL -> model = value.blob
Tag.ATTESTATION_ID_IMEI -> imei1 = value.blob
Tag.ATTESTATION_ID_SECOND_IMEI -> imei2 = value.blob
Tag.ATTESTATION_ID_MEID -> meid = value.blob
}
}
}
fun setEcCurveName(curveSize: Int) {
ecCurveName = when (curveSize) {
224 -> "secp224r1"
256 -> "secp256r1"
384 -> "secp384r1"
521 -> "secp521r1"
else -> "secp256r1"
}
}
companion object {
private fun getEcCurveName(curve: Int): String = when (curve) {
EcCurve.CURVE_25519 -> "CURVE_25519"
EcCurve.P_224 -> "secp224r1"
EcCurve.P_256 -> "secp256r1"
EcCurve.P_384 -> "secp384r1"
EcCurve.P_521 -> "secp521r1"
else -> throw IllegalArgumentException("Unknown EC curve: $curve")
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as KeyGenParameters
return keySize == other.keySize &&
algorithm == other.algorithm &&
certificateSerial == other.certificateSerial &&
certificateNotBefore == other.certificateNotBefore &&
certificateNotAfter == other.certificateNotAfter &&
certificateSubject == other.certificateSubject &&
rsaPublicExponent == other.rsaPublicExponent &&
ecCurve == other.ecCurve &&
ecCurveName == other.ecCurveName &&
purpose == other.purpose &&
digest == other.digest &&
attestationChallenge.contentEquals(other.attestationChallenge) &&
brand.contentEquals(other.brand) &&
device.contentEquals(other.device) &&
product.contentEquals(other.product) &&
manufacturer.contentEquals(other.manufacturer) &&
model.contentEquals(other.model) &&
imei1.contentEquals(other.imei1) &&
imei2.contentEquals(other.imei2) &&
meid.contentEquals(other.meid) &&
serialno.contentEquals(other.serialno)
}
override fun hashCode(): Int {
var result = keySize
result = 31 * result + algorithm
result = 31 * result + (certificateSerial?.hashCode() ?: 0)
result = 31 * result + (certificateNotBefore?.hashCode() ?: 0)
result = 31 * result + (certificateNotAfter?.hashCode() ?: 0)
result = 31 * result + (certificateSubject?.hashCode() ?: 0)
result = 31 * result + (rsaPublicExponent?.hashCode() ?: 0)
result = 31 * result + ecCurve
result = 31 * result + (ecCurveName?.hashCode() ?: 0)
result = 31 * result + purpose.hashCode()
result = 31 * result + digest.hashCode()
result = 31 * result + (attestationChallenge?.contentHashCode() ?: 0)
result = 31 * result + (brand?.contentHashCode() ?: 0)
result = 31 * result + (device?.contentHashCode() ?: 0)
result = 31 * result + (product?.contentHashCode() ?: 0)
result = 31 * result + (manufacturer?.contentHashCode() ?: 0)
result = 31 * result + (model?.contentHashCode() ?: 0)
result = 31 * result + (imei1?.contentHashCode() ?: 0)
result = 31 * result + (imei2?.contentHashCode() ?: 0)
result = 31 * result + (meid?.contentHashCode() ?: 0)
result = 31 * result + (serialno?.contentHashCode() ?: 0)
return result
}
}
private val keyboxes = ConcurrentHashMap<String, KeyBox>()
private val leafAlgorithms = ConcurrentHashMap<KeyIdentifier, String>()
fun hasKeyboxes(): Boolean = keyboxes.isNotEmpty()
private data class Digest(val digest: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Digest
return digest.contentEquals(other.digest)
}
override fun hashCode(): Int = digest.contentHashCode()
}
fun String.sanitizeXml(): String {
var content = this
val boms = listOf(
"\uFEFF",
"\uFFFE",
"\u0000\uFEFF"
)
content = content.trimStart()
for (bom in boms) {
content = content.removePrefix(bom)
}
content = content.trimStart()
return content.trimEnd()
}
fun readFromXml(xmlData: String?) {
keyboxes.clear()
leafAlgorithms.clear()
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)
}
Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
} catch (t: Throwable) {
Logger.e("Error loading XML file (keyboxes cleared)", t)
}
}
private fun processKeybox(xmlParser: XmlParser, index: Int) {
try {
val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
val keyboxAlgorithm = when (algorithmResult) {
is XmlParser.ParseResult.Success -> algorithmResult.attributes["algorithm"]
?: throw IllegalArgumentException("Missing algorithm attribute")
is XmlParser.ParseResult.Error -> throw Exception(algorithmResult.message, algorithmResult.cause)
}
val privateKeyResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
val privateKeyContent = when (privateKeyResult) {
is XmlParser.ParseResult.Success -> privateKeyResult.attributes["text"]
?: throw IllegalArgumentException("Missing private key text")
is XmlParser.ParseResult.Error -> throw Exception(privateKeyResult.message, privateKeyResult.cause)
}
val numberOfCertificatesResult = xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
)
val numberOfCertificates = when (numberOfCertificatesResult) {
is XmlParser.ParseResult.Success -> numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
?: throw IllegalArgumentException("Invalid number of certificates")
is XmlParser.ParseResult.Error -> throw Exception(numberOfCertificatesResult.message, numberOfCertificatesResult.cause)
}
val certificateChain = mutableListOf<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)
}
when (val certParseResult = CertificateUtils.parseCertificate(certContent)) {
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 keyPair = CertificateUtils.convertPemToKeyPair(pemKeyPair)
val algorithmName = when (keyboxAlgorithm.lowercase()) {
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
else -> keyboxAlgorithm
}
keyboxes[algorithmName] = KeyBox(pemKeyPair, keyPair, certificateChain)
} catch (t: Throwable) {
Logger.e("Error processing keybox $index", t)
throw t
}
}
fun hackCertificateChain(certificateChain: Array<Certificate>?): 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 leafHolder = X509CertificateHolder(leaf.encoded)
val extension = leafHolder.getExtension(ATTESTATION_OID)
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
val encodables = sequence.toArray()
val teeEnforced = encodables[7] as ASN1Sequence
val vector = ASN1EncodableVector()
var rootOfTrust: ASN1Encodable? = null
teeEnforced.forEach { element ->
val taggedObject = element as ASN1TaggedObject
if (taggedObject.tagNo == 704) {
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
vector.add(taggedObject)
}
}
val keybox = keyboxes[leaf.publicKey.algorithm]
?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
val certificates = LinkedList(keybox.certificates)
val builder = X509v3CertificateBuilder(
X509CertificateHolder(certificates[0].encoded).subject,
leafHolder.serialNumber,
leafHolder.notBefore,
leafHolder.notAfter,
leafHolder.subject,
leafHolder.subjectPublicKeyInfo
)
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
builder.addExtension(hackedExtension)
leafHolder.extensions.extensionOIDs.forEach { oid ->
if (oid.id != ATTESTATION_OID.id) {
builder.addExtension(leafHolder.getExtension(oid))
}
}
certificates.addFirst(JcaX509CertificateConverter().getCertificate(builder.build(signer)))
certificates.toTypedArray()
} catch (t: Throwable) {
Logger.e("Failed to hack certificate chain", 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 keybox = keyboxes[algorithm]
?: throw UnsupportedOperationException("Unsupported algorithm: $algorithm")
CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
} catch (t: Throwable) {
Logger.e("Failed to hack CA certificate chain", t)
caList
}
}
fun 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 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) {
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
vector.add(taggedObject)
}
}
val keybox = keyboxes[leaf.publicKey.algorithm]
?: throw UnsupportedOperationException("Unsupported algorithm: ${leaf.publicKey.algorithm}")
val builder = X509v3CertificateBuilder(
X509CertificateHolder(keybox.certificates[0].encoded).subject,
leafHolder.serialNumber,
leafHolder.notBefore,
leafHolder.notAfter,
leafHolder.subject,
leafHolder.subjectPublicKeyInfo
)
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
val hackedExtension = createHackedAttestationExtension(rootOfTrust, vector, encodables)
builder.addExtension(hackedExtension)
leafHolder.extensions.extensionOIDs.forEach { oid ->
if (oid.id != ATTESTATION_OID.id) {
builder.addExtension(leafHolder.getExtension(oid))
}
}
JcaX509CertificateConverter().getCertificate(builder.build(signer)).encoded
} catch (t: Throwable) {
Logger.e("Failed to hack user certificate", t)
certificate
}
}
fun generateKeyPair(params: KeyGenParameters): KeyPair? = runCatching {
when (params.algorithm) {
Algorithm.EC -> {
Logger.d("Generating EC keypair of size ${params.keySize}")
buildECKeyPair(params)
}
Algorithm.RSA -> {
Logger.d("Generating RSA keypair of size ${params.keySize}")
buildRSAKeyPair(params)
}
else -> {
Logger.e("Unsupported algorithm: ${params.algorithm}")
null
}
}
}.onFailure {
Logger.e("Failed to generate key pair", it)
}.getOrNull()
fun generateChain(uid: Int, params: KeyGenParameters, keyPair: KeyPair, securityLevel: Int = 1): List<ByteArray>? = runCatching {
val keybox = getKeyboxForAlgorithm(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 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(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 buildECKeyPair(params: KeyGenParameters): KeyPair {
setupBouncyCastle()
val spec = ECGenParameterSpec(params.ecCurveName)
val keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME)
keyPairGenerator.initialize(spec)
return keyPairGenerator.generateKeyPair()
}
private fun buildRSAKeyPair(params: KeyGenParameters): KeyPair {
setupBouncyCastle()
val spec = RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
val keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME)
keyPairGenerator.initialize(spec)
return keyPairGenerator.generateKeyPair()
}
private fun setupBouncyCastle() {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
}
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(algorithm: Int): KeyBox? {
val algorithmName = mapAlgorithmToName(algorithm) ?: return null
return keyboxes[algorithmName]
}
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
Pair(keyInfo.first, issuer)
} else {
Logger.e("Attestation key info not found, falling back to default keybox")
null
}
}
private fun createHackedAttestationExtension(
originalRootOfTrust: ASN1Encodable?,
vector: ASN1EncodableVector,
originalEncodables: Array<ASN1Encodable>
): Extension {
val verifiedBootKey = bootKey
var verifiedBootHash: ByteArray? = null
try {
if (originalRootOfTrust is ASN1Sequence) {
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 = getBootHashFromProp()
}
val rootOfTrustElements = arrayOf(
DEROctetString(verifiedBootKey),
ASN1Boolean.TRUE,
ASN1Enumerated(0),
DEROctetString(verifiedBootHash)
)
val hackedRootOfTrust = DERSequence(rootOfTrustElements)
vector.add(DERTaggedObject(true, 718, ASN1Integer(vendorPatchLevelLong.toLong())))
vector.add(DERTaggedObject(true, 719, ASN1Integer(bootPatchLevelLong.toLong())))
vector.add(DERTaggedObject(true, 706, ASN1Integer(patchLevel.toLong())))
vector.add(DERTaggedObject(true, 705, ASN1Integer(osVersion.toLong())))
vector.add(DERTaggedObject(704, hackedRootOfTrust))
val hackEnforced = DERSequence(vector)
originalEncodables[7] = hackEnforced
val hackedSequence = DERSequence(originalEncodables)
val hackedSequenceOctets = DEROctetString(hackedSequence)
return Extension(ATTESTATION_OID, false, hackedSequenceOctets)
}
private fun buildCertificate(
keyPair: KeyPair,
keybox: KeyBox,
params: KeyGenParameters,
issuer: X500Name,
uid: Int,
securityLevel: Int = 1,
signingKeyPair: KeyPair = keybox.keyPair
): Certificate {
val builder = JcaX509v3CertificateBuilder(
issuer,
params.certificateSerial ?: BigInteger.ONE,
params.certificateNotBefore ?: Date(),
params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
keyPair.public
)
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
builder.addExtension(createAttestationExtension(params, uid, securityLevel))
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 createAttestationExtension(params: KeyGenParameters, uid: Int, securityLevel: Int = 1): Extension {
try {
val key = bootKey
val hash = getBootHashFromProp()
Logger.d("Using boothash ${hash?.toHex() ?: 0}")
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(io.github.beakthoven.TrickyStoreOSS.osVersion.toLong())
val osPatchLevel = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.patchLevel.toLong())
val applicationID = createApplicationId(uid)
val bootPatchLevel = ASN1Integer(bootPatchLevelLong.toLong())
val vendorPatchLevel = ASN1Integer(vendorPatchLevelLong.toLong())
val creationDateTime = ASN1Integer(System.currentTimeMillis())
val origin = ASN1Integer(0L)
val moduleHash = DEROctetString(io.github.beakthoven.TrickyStoreOSS.moduleHash)
val teeEnforcedObjects = mutableListOf(
DERTaggedObject(true, 1, purpose),
DERTaggedObject(true, 2, algorithm),
DERTaggedObject(true, 3, keySize),
DERTaggedObject(true, 5, digest),
DERTaggedObject(true, 10, ecCurve),
DERTaggedObject(true, 503, noAuthRequired),
DERTaggedObject(true, 702, origin),
DERTaggedObject(true, 704, rootOfTrustSeq),
DERTaggedObject(true, 705, osVersion),
DERTaggedObject(true, 706, osPatchLevel),
DERTaggedObject(true, 718, vendorPatchLevel),
DERTaggedObject(true, 719, bootPatchLevel),
)
if (io.github.beakthoven.TrickyStoreOSS.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))) }
if (io.github.beakthoven.TrickyStoreOSS.attestVersion >= 300) {
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)
)
return Extension(
ATTESTATION_OID,
false,
getAsn1OctetString(teeEnforcedObjects.toTypedArray(), softwareEnforcedObjects, params, securityLevel)
)
} catch (t: Throwable) {
Logger.e("Failed to create attestation extension", t)
throw t
}
}
private fun getAsn1OctetString(
teeEnforcedEncodables: Array<ASN1Encodable>,
softwareEnforcedEncodables: Array<ASN1Encodable>,
params: KeyGenParameters,
securityLevel: Int = 1
): ASN1OctetString {
val attestationVersion = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.attestVersion.toLong())
val attestationSecurityLevel = ASN1Enumerated(securityLevel)
val keymasterVersion = ASN1Integer(io.github.beakthoven.TrickyStoreOSS.keymasterVersion.toLong())
val keymasterSecurityLevel = ASN1Enumerated(securityLevel)
val attestationChallenge = DEROctetString(params.attestationChallenge ?: ByteArray(0))
val uniqueId = DEROctetString(ByteArray(0))
val softwareEnforced = DERSequence(softwareEnforcedEncodables)
val teeEnforced = DERSequence(teeEnforcedEncodables)
val keyDescriptionEncodables = arrayOf(
attestationVersion,
attestationSecurityLevel,
keymasterVersion,
keymasterSecurityLevel,
attestationChallenge,
uniqueId,
softwareEnforced,
teeEnforced
)
val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
return DEROctetString(keyDescriptionSeq.encoded)
}
@Throws(Throwable::class)
private fun createApplicationId(uid: Int): DEROctetString {
val pm = Config.getPm() ?: throw IllegalStateException("PackageManager not found!")
val packages = pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
val 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)
}
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 signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
val applicationIdArray = arrayOf(
DERSet(packageInfoArray),
DERSet(signaturesArray)
)
return DEROctetString(DERSequence(applicationIdArray).encoded)
}
}
data class AttestationData(
val verifiedBootHash: ByteArray?,
val attestVersion: Int?,
val keymasterVersion: Int?,
val osVersion: Int?,
)
val keygen_alias = "tricky_store_oss_attest"
val teeStatus: Boolean by lazy { isTEEWorking() }
private fun isTEEWorking(): Boolean {
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
android.app.ActivityThread.initializeMainlineModules()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
android.security.keystore2.AndroidKeyStoreProvider.install()
} else {
android.security.keystore.AndroidKeyStoreProvider.install()
}
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val challenge = ByteArray(16).apply {
SecureRandom().nextBytes(this)
}
val parameterSpec = KeyGenParameterSpec.Builder(
keygen_alias,
KeyProperties.PURPOSE_SIGN
)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.setIsStrongBoxBacked(false)
.build()
keyPairGenerator.initialize(parameterSpec)
keyPairGenerator.generateKeyPair()
Logger.d("TEE check: successful")
// keyStore.deleteEntry(keygen_alias)
true
} catch (e: Exception) {
Logger.w("TEE check failure: ${e.message}")
false
}
}
private fun getAttestCert(): X509Certificate? {
return if (teeStatus) {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val certChain = keyStore.getCertificateChain(keygen_alias)
if (certChain == null || certChain.isEmpty()) {
null
} else {
keyStore.deleteEntry(keygen_alias)
certChain[0] as X509Certificate
}
} else {
null
}
}
fun getAttestData(): AttestationData? {
val leaf: X509Certificate = getAttestCert() ?: return null
val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
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 keyDescriptionSeq = ASN1Sequence.getInstance(ext.extnValue.octets)
val encodables = keyDescriptionSeq.toArray()
val attestVersion = ASN1Integer.getInstance(encodables[0]).value.intValueExact()
val keymasterVersion = ASN1Integer.getInstance(encodables[2]).value.intValueExact()
var attestVerifiedBootHash: ByteArray? = null
var attestOSVersion: Int? = null
val teeEnforced = ASN1Sequence.getInstance(encodables[7])
teeEnforced.forEach { element ->
val tagged = element as ASN1TaggedObject
when (tagged.tagNo) {
704 -> { // Parse Root of Trust
val rootOfTrustSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
if (rootOfTrustSeq.size() >= 4) {
attestVerifiedBootHash = ASN1OctetString.getInstance(rootOfTrustSeq.getObjectAt(3)).octets
}
}
705 -> { // Parse OS Version
attestOSVersion = ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive()).value.intValueExact()
}
}
}
Logger.i("Extracted attestationVersion: $attestVersion")
Logger.i("Extracted keymasterVersion: $keymasterVersion")
Logger.i("Extracted verifiedBootHash: ${attestVerifiedBootHash?.toHex() ?: 0}")
Logger.i("Extracted osVersion: $attestOSVersion")
AttestationData(
verifiedBootHash = attestVerifiedBootHash,
attestVersion = attestVersion,
keymasterVersion = keymasterVersion,
osVersion = attestOSVersion
)
} catch (e: Exception) {
Logger.e("Failed to parse attestation data", e)
null
}
}
@@ -6,10 +6,10 @@
package io.github.beakthoven.TrickyStoreOSS
import android.os.Build
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.Keystore2Interceptor
import io.github.beakthoven.TrickyStoreOSS.interceptors.KeystoreInterceptor
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
private const val RETRY_DELAY_MS = 1000L
private const val SERVICE_SLEEP_MS = 1000000L
@@ -18,7 +18,7 @@ fun main(args: Array<String>) {
Logger.i("Welcome to TrickyStoreOSS!")
try {
setupBootHash()
AndroidUtils.setupBootHash()
initializeInterceptors()
maintainService()
} catch (e: Exception) {
@@ -35,7 +35,7 @@ private fun initializeInterceptors() {
Thread.sleep(RETRY_DELAY_MS)
}
Config.initialize()
PkgConfig.initialize()
Logger.i("Interceptors initialized successfully")
}
@@ -5,11 +5,17 @@
package io.github.beakthoven.TrickyStoreOSS
import android.security.keystore.KeyProperties
import io.github.beakthoven.TrickyStoreOSS.CertificateGen.KeyBox
import io.github.beakthoven.TrickyStoreOSS.CertificateHack.clearLeafAlgorithms
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.IOException
import java.io.StringReader
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
class XmlParser(private val xmlContent: String) {
@@ -152,4 +158,117 @@ class XmlParser(private val xmlContent: String) {
}
}
fun String.toXmlParser(): XmlParser = XmlParser(this)
object KeyBoxUtils {
val keyboxes = ConcurrentHashMap<String, KeyBox>()
fun hasKeyboxes(): Boolean = keyboxes.isNotEmpty()
fun readFromXml(xmlData: String?) {
keyboxes.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)
}
Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
} catch (t: Throwable) {
Logger.e("Error loading XML file (keyboxes cleared)", t)
}
}
private fun String.sanitizeXml(): String {
var content = this
val boms = listOf(
"\uFEFF",
"\uFFFE",
"\u0000\uFEFF"
)
content = content.trimStart()
for (bom in boms) {
content = content.removePrefix(bom)
}
content = content.trimStart()
return content.trimEnd()
}
private fun processKeybox(xmlParser: XmlParser, index: Int) {
try {
val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
val keyboxAlgorithm = when (algorithmResult) {
is XmlParser.ParseResult.Success -> algorithmResult.attributes["algorithm"]
?: throw IllegalArgumentException("Missing algorithm attribute")
is XmlParser.ParseResult.Error -> throw Exception(algorithmResult.message, algorithmResult.cause)
}
val privateKeyResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
val privateKeyContent = when (privateKeyResult) {
is XmlParser.ParseResult.Success -> privateKeyResult.attributes["text"]
?: throw IllegalArgumentException("Missing private key text")
is XmlParser.ParseResult.Error -> throw Exception(privateKeyResult.message, privateKeyResult.cause)
}
val numberOfCertificatesResult = xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
)
val numberOfCertificates = when (numberOfCertificatesResult) {
is XmlParser.ParseResult.Success -> numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
?: throw IllegalArgumentException("Invalid number of certificates")
is XmlParser.ParseResult.Error -> throw Exception(numberOfCertificatesResult.message, numberOfCertificatesResult.cause)
}
val certificateChain = mutableListOf<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)
}
when (val certParseResult = CertificateUtils.parseCertificate(certContent)) {
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 keyPair = CertificateUtils.convertPemToKeyPair(pemKeyPair)
val algorithmName = when (keyboxAlgorithm.lowercase()) {
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
else -> keyboxAlgorithm
}
keyboxes[algorithmName] = KeyBox(pemKeyPair, keyPair, certificateChain)
} catch (t: Throwable) {
Logger.e("Error processing keybox $index", t)
throw t
}
}
}
@@ -3,19 +3,19 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.core.config
package io.github.beakthoven.TrickyStoreOSS.config
import android.content.pm.IPackageManager
import android.os.FileObserver
import android.os.IBinder
import android.os.IInterface
import android.os.ServiceManager
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.teeStatus
import io.github.beakthoven.TrickyStoreOSS.AttestUtils.TEEStatus
import io.github.beakthoven.TrickyStoreOSS.KeyBoxUtils
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.io.File
object Config {
object PkgConfig {
private val hackPackages = mutableSetOf<String>()
private val generatePackages = mutableSetOf<String>()
private val packageModes = mutableMapOf<String, Mode>()
@@ -55,7 +55,7 @@ object Config {
}
private fun updateKeyBox(f: File?) = runCatching {
CertificateHacker.readFromXml(f?.readText())
KeyBoxUtils.readFromXml(f?.readText())
}.onFailure {
Logger.e("failed to update keybox", it)
}
@@ -72,7 +72,7 @@ object Config {
private fun storeTEEStatus(root: File) {
val statusFile = File(root, TEE_STATUS_FILE)
teeBroken = !teeStatus
teeBroken = !TEEStatus
try {
statusFile.writeText("teeBroken=${teeBroken}")
Logger.i("TEE status written to $statusFile: teeBroken=$teeBroken")
@@ -8,7 +8,7 @@ package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.os.Binder
import android.os.IBinder
import android.os.Parcel
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
open class BinderInterceptor : Binder() {
@@ -11,7 +11,7 @@ import android.os.Parcelable
import android.os.ServiceManager
import android.security.KeyStore
import android.security.keystore.KeystoreResponse
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import kotlin.system.exitProcess
abstract class BaseKeystoreInterceptor : BinderInterceptor() {
@@ -96,6 +96,10 @@ abstract class BaseKeystoreInterceptor : BinderInterceptor() {
object InterceptorUtils {
fun getTransactCode(clazz: Class<*>, method: String): Int =
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }
.getInt(null)
fun createSuccessKeystoreResponse(): KeystoreResponse {
val parcel = Parcel.obtain()
try {
@@ -12,13 +12,14 @@ import android.os.Parcel
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.CertificateHack
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.KeyBoxUtils
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
@SuppressLint("BlockedPrivateApi")
@@ -74,17 +75,17 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
data: Parcel
): Result {
if (code == getKeyEntryTransaction) {
if (CertificateHacker.hasKeyboxes()) {
if (KeyBoxUtils.hasKeyboxes()) {
Logger.d("intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}")
kotlin.runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
if (Config.needGenerate(callingUid)) {
if (PkgConfig.needGenerate(callingUid)) {
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
?: return@runCatching
Logger.i("Generate key for uid=$callingUid alias=${descriptor.alias}")
return createTypedObjectReply(response)
} else if (Config.needHack(callingUid)) {
} else if (PkgConfig.needHack(callingUid)) {
if (SecurityLevelInterceptor.shouldSkipLeafHack(callingUid, descriptor.alias)) {
Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
val response = SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
@@ -138,7 +139,7 @@ object Keystore2Interceptor : BaseKeystoreInterceptor() {
if (response != null) {
val chain = CertificateUtils.run { response.getCertificateChain() }
if (chain != null) {
val newChain = CertificateHacker.hackCertificateChain(chain)
val newChain = CertificateHack.hackCertificateChain(chain)
response.putCertificateChain(newChain).getOrThrow()
Logger.i("Hacked certificate for uid=$callingUid")
return createTypedObjectReply(response)
@@ -19,15 +19,17 @@ import android.security.keystore.IKeystoreCertificateChainCallback
import android.security.keystore.IKeystoreExportKeyCallback
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
import android.security.keystore.IKeystoreService
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.CertificateGen
import io.github.beakthoven.TrickyStoreOSS.CertificateHack
import io.github.beakthoven.TrickyStoreOSS.KeyBoxUtils
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.math.BigInteger
import java.security.KeyPair
import java.util.Date
@@ -51,7 +53,7 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
private val keyArguments = HashMap<Key, CertificateHacker.KeyGenParameters>()
private val keyArguments = HashMap<Key, CertificateGen.KeyGenParameters>()
private val keyPairs = HashMap<Key, KeyPair>()
data class Key(val uid: Int, val alias: String)
@@ -64,14 +66,14 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
callingPid: Int,
data: Parcel
): Result {
if (CertificateHacker.hasKeyboxes()) {
if (KeyBoxUtils.hasKeyboxes()) {
if (code == getTransaction) {
if (Config.needHack(callingUid)) {
if (PkgConfig.needHack(callingUid)) {
return Continue
} else if (Config.needGenerate(callingUid)) {
} else if (PkgConfig.needGenerate(callingUid)) {
return Skip
}
} else if (Config.needGenerate(callingUid)) {
} else if (PkgConfig.needGenerate(callingUid)) {
when (code) {
generateKeyTransaction -> {
kotlin.runCatching {
@@ -81,12 +83,12 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
val check = data.readInt()
val kma = KeymasterArguments()
val kgp = CertificateHacker.KeyGenParameters()
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.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())
@@ -146,7 +148,7 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
val alias = data.readString()!!.extractAlias()
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
val kp = CertificateHacker.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
val kp = CertificateGen.generateKeyPair(keyArguments[Key(callingUid, alias)]!!)
keyPairs[Key(callingUid, alias)] = kp!!
val erP = Parcel.obtain()
@@ -181,7 +183,7 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
val key = Key(callingUid, alias)
val ka = keyArguments[key]!!
ka.attestationChallenge = attestationChallenge
val chain = CertificateHacker.generateChain(callingUid, ka, keyPairs[key]!!)
val chain = CertificateGen.generateChain(callingUid, ka, keyPairs[key]!!)
val kcc = KeymasterCertificateChain(chain)
callback.onFinished(ksr, kcc)
@@ -218,12 +220,12 @@ object KeystoreInterceptor : BaseKeystoreInterceptor() {
var response = reply.createByteArray()
when {
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
response = CertificateHacker.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 = CertificateHacker.hackCACertificateChain(response!!, alias.extractAlias(), callingUid)
response = CertificateHack.hackCACertificateChain(response!!, alias.extractAlias(), callingUid)
Logger.i("Hacked CA certificate chain for uid=$callingUid")
return createByteArrayReply(response)
}
@@ -16,10 +16,10 @@ import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata
import androidx.annotation.Keep
import io.github.beakthoven.TrickyStoreOSS.CertificateHacker
import io.github.beakthoven.TrickyStoreOSS.core.config.Config
import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.CertificateGen
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
import java.security.KeyPair
import java.security.cert.Certificate
@@ -80,9 +80,9 @@ class SecurityLevelInterceptor(
val params = data.createTypedArray(KeyParameter.CREATOR)!!
val aFlags = data.readInt()
val entropy = data.createByteArray()
val kgp = CertificateHacker.KeyGenParameters(params)
if (Config.needGenerate(callingUid)) {
val pair = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp, level)
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)
@@ -91,10 +91,10 @@ class SecurityLevelInterceptor(
p.writeNoException()
p.writeTypedObject(response.metadata, 0)
return OverrideReply(0, p)
} else if (Config.needHack(callingUid)) {
} 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 = CertificateHacker.generateKeyPair(callingUid, keyDescriptor, attestationKeyDescriptor, kgp, level)
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)
@@ -119,7 +119,7 @@ class SecurityLevelInterceptor(
private fun buildResponse(
chain: List<Certificate>,
params: CertificateHacker.KeyGenParameters,
params: CertificateGen.KeyGenParameters,
descriptor: KeyDescriptor
): KeyEntryResponse {
val response = KeyEntryResponse()
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.core.logging
package io.github.beakthoven.TrickyStoreOSS.logging
import android.util.Log
@@ -27,7 +27,7 @@ object Logger {
}
fun e(message: String, throwable: Throwable) {
Log.e(TAG, "wtf: $message", throwable)
Log.e(TAG, "fatal: $message", throwable)
}
fun i(message: String) {