diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt index e32a718..b2972e7 100644 --- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt +++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/AndroidUtils.kt @@ -23,20 +23,61 @@ fun getTransactCode(clazz: Class<*>, method: String): Int = clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true } .getInt(null) -val bootHash: ByteArray by lazy { - getBootHashFromProp() ?: randomBytes() +// 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) -private fun getBootHashFromProp(): ByteArray? { +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) } @@ -114,9 +155,6 @@ private fun parsePatchLevelValue(value: String, component: String, isLong: Boole } } -val osVersion: Int - get() = getOsVersion(Build.VERSION.SDK_INT) - private val osVersionMap = mapOf( Build.VERSION_CODES.BAKLAVA to 160000, Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000, @@ -128,7 +166,8 @@ private val osVersionMap = mapOf( Build.VERSION_CODES.Q to 100000 ) -private fun getOsVersion(sdkVersion: Int): Int = osVersionMap[sdkVersion] ?: 160000 +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 @@ -142,11 +181,10 @@ private val attestVersionMap = mapOf( ) val attestVersion: Int - get() = attestVersionMap[Build.VERSION.SDK_INT] ?: 400 + get() = cachedAttestData?.attestVersion ?: attestVersionMap[Build.VERSION.SDK_INT] ?: 400 val keymasterVersion: Int - get() = if (attestVersion == 4) 41 else attestVersion - + get() = cachedAttestData?.keymasterVersion ?: if (attestVersion == 4) 41 else attestVersion fun String.convertPatchLevel(isLong: Boolean): Int = runCatching { val parts = split("-") @@ -195,4 +233,5 @@ val moduleHash: ByteArray by lazy { } } -fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() } \ No newline at end of file +fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() } +fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) } \ No newline at end of file diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt index 8d92a25..ac5efa2 100644 --- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt +++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/CertificateHacker.kt @@ -10,14 +10,28 @@ 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.* import io.github.beakthoven.TrickyStoreOSS.core.config.Config import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor -import org.bouncycastle.asn1.* +import org.bouncycastle.asn1.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 @@ -31,16 +45,21 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import java.io.ByteArrayInputStream import java.math.BigInteger import java.nio.charset.StandardCharsets -import java.security.* +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.* +import java.util.Date +import java.util.LinkedList import java.util.concurrent.ConcurrentHashMap import javax.security.auth.x500.X500Principal -import android.os.Build object CertificateHacker { @@ -625,7 +644,7 @@ object CertificateHacker { } if (verifiedBootHash == null) { - verifiedBootHash = bootHash + verifiedBootHash = getBootHashFromProp() } val rootOfTrustElements = arrayOf( @@ -684,7 +703,9 @@ object CertificateHacker { private fun createAttestationExtension(params: KeyGenParameters, uid: Int, securityLevel: Int = 1): Extension { try { val key = bootKey - val hash = bootHash + val hash = getBootHashFromProp() + + Logger.d("Using boothash ${hash?.toHex() ?: 0}") val rootOfTrustEncodables = arrayOf( DEROctetString(key), @@ -834,4 +855,130 @@ object CertificateHacker { 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 + } } \ No newline at end of file diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/Main.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/Main.kt index d0a110f..503ef7c 100644 --- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/Main.kt +++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/Main.kt @@ -18,6 +18,7 @@ fun main(args: Array) { Logger.i("Welcome to TrickyStoreOSS!") try { + setupBootHash() initializeInterceptors() maintainService() } catch (e: Exception) { diff --git a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt index fdb8082..1b4211b 100644 --- a/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt +++ b/app/src/main/java/io/github/beakthoven/TrickyStoreOSS/core/config/Config.kt @@ -6,18 +6,12 @@ package io.github.beakthoven.TrickyStoreOSS.core.config import android.content.pm.IPackageManager -import android.os.Build import android.os.FileObserver import android.os.ServiceManager -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties import io.github.beakthoven.TrickyStoreOSS.CertificateHacker import io.github.beakthoven.TrickyStoreOSS.core.logging.Logger +import io.github.beakthoven.TrickyStoreOSS.teeStatus import java.io.File -import java.security.KeyPairGenerator -import java.security.KeyStore -import java.security.SecureRandom -import java.security.spec.ECGenParameterSpec object Config { private val hackPackages = mutableSetOf() @@ -74,58 +68,12 @@ object Config { @Volatile private var teeBroken: Boolean? = null - private fun isTEEWorking(): Boolean { - val alias = "tee_attest_test_key" - return try { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - android.app.ActivityThread.initializeMainlineModules(); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - android.security.keystore2.AndroidKeyStoreProvider.install(); - } else { - android.security.keystore.AndroidKeyStoreProvider.install(); - } - - val keyStore = KeyStore.getInstance("AndroidKeyStore") - keyStore.load(null) - - val keyPairGenerator = KeyPairGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore") - - val challenge = ByteArray(16).apply { - SecureRandom().nextBytes(this) - } - - val parameterSpec = KeyGenParameterSpec.Builder( - alias, - KeyProperties.PURPOSE_SIGN - ) - .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) - .setDigests(KeyProperties.DIGEST_SHA256) - .setAttestationChallenge(challenge) - .setIsStrongBoxBacked(false) - .build() - - keyPairGenerator.initialize(parameterSpec) - keyPairGenerator.generateKeyPair() - - keyStore.deleteEntry(alias) - true - } catch (e: Exception) { - Logger.e("TEE check failure: ${e.message}") - false - } - } - - private fun storeTEEStatus(root: File) { val statusFile = File(root, TEE_STATUS_FILE) - val status = isTEEWorking() - teeBroken = !status + teeBroken = !teeStatus try { - statusFile.writeText("teeBroken=${!status}") + statusFile.writeText("teeBroken=${teeBroken}") + Logger.i("TEE status written to $statusFile: teeBroken=$teeBroken") } catch (e: Exception) { Logger.e("Failed to write TEE status: ${e.message}") } diff --git a/stub/src/main/java/android/os/SystemProperties.java b/stub/src/main/java/android/os/SystemProperties.java index 0cd11e1..50e74a8 100644 --- a/stub/src/main/java/android/os/SystemProperties.java +++ b/stub/src/main/java/android/os/SystemProperties.java @@ -9,4 +9,8 @@ public class SystemProperties { public static String get(String key, String def) { throw new RuntimeException(""); } + + public static void set(String key, String val) { + throw new RuntimeException(""); + } }