Skip to content

Commit

Permalink
use PackageInfoCompat instead of dealing with signatures manually
Browse files Browse the repository at this point in the history
  • Loading branch information
Axelen123 committed Aug 12, 2024
1 parent 3f497a9 commit 886ceaf
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 65 deletions.
8 changes: 4 additions & 4 deletions app/schemas/app.revanced.manager.data.room.AppDatabase/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "98837fd72fde0272894bce063c1095af",
"identityHash": "ab113134d89f2c5e412e87775510b327",
"entities": [
{
"tableName": "patch_bundles",
Expand Down Expand Up @@ -405,7 +405,7 @@
},
{
"tableName": "trusted_downloader_plugins",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` TEXT NOT NULL, PRIMARY KEY(`package_name`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` BLOB NOT NULL, PRIMARY KEY(`package_name`))",
"fields": [
{
"fieldPath": "packageName",
Expand All @@ -416,7 +416,7 @@
{
"fieldPath": "signature",
"columnName": "signature",
"affinity": "TEXT",
"affinity": "BLOB",
"notNull": true
}
],
Expand All @@ -433,7 +433,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '98837fd72fde0272894bce063c1095af')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab113134d89f2c5e412e87775510b327')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "trusted_downloader_plugins")
data class TrustedDownloaderPlugin(
class TrustedDownloaderPlugin(
@PrimaryKey @ColumnInfo(name = "package_name") val packageName: String,
@ColumnInfo(name = "signature") val signature: String
@ColumnInfo(name = "signature") val signature: ByteArray
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.room.Upsert
@Dao
interface TrustedDownloaderPluginDao {
@Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName")
suspend fun getTrustedSignature(packageName: String): String?
suspend fun getTrustedSignature(packageName: String): ByteArray?

@Upsert
suspend fun upsertTrust(plugin: TrustedDownloaderPlugin)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package app.revanced.manager.domain.repository

import android.content.Context
import android.app.Application
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.util.Log
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
Expand All @@ -30,7 +29,7 @@ import java.lang.reflect.Modifier
class DownloaderPluginRepository(
private val pm: PM,
private val prefs: PreferencesManager,
private val context: Context,
private val app: Application,
db: AppDatabase
) {
private val trustDao = db.trustedDownloaderPluginDao()
Expand All @@ -50,16 +49,14 @@ class DownloaderPluginRepository(
}

suspend fun reload() {
val pluginPackages =
val plugins =
withContext(Dispatchers.IO) {
pm.getPackagesWithFeature(
PLUGIN_FEATURE,
flags = packageFlags
)
pm.getPackagesWithFeature(PLUGIN_FEATURE)
.associate { it.packageName to loadPlugin(it.packageName) }
}

_pluginStates.value = pluginPackages.associate { it.packageName to loadPlugin(it) }
installedPluginPackageNames.value = pluginPackages.map { it.packageName }.toSet()
_pluginStates.value = plugins
installedPluginPackageNames.value = plugins.keys

val acknowledgedPlugins = acknowledgedDownloaderPlugins.get()
val uninstalledPlugins = acknowledgedPlugins subtract installedPluginPackageNames.value
Expand All @@ -78,41 +75,40 @@ class DownloaderPluginRepository(
return plugin to app.unwrapWith(plugin)
}

private suspend fun loadPlugin(packageInfo: PackageInfo): DownloaderPluginState {
private suspend fun loadPlugin(packageName: String): DownloaderPluginState {
try {
if (!verify(packageInfo)) return DownloaderPluginState.Untrusted
if (!verify(packageName)) return DownloaderPluginState.Untrusted
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Log.e(tag, "Got exception while verifying plugin ${packageInfo.packageName}", e)
Log.e(tag, "Got exception while verifying plugin $packageName", e)
return DownloaderPluginState.Failed(e)
}

val downloaderContext = DownloaderContext(
androidContext = context.createPackageContext(
packageInfo.packageName,
0
),
pluginHostPackageName = context.packageName
)

return try {
val className =
packageInfo.applicationInfo.metaData.getString(METADATA_PLUGIN_CLASS)
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
val packageInfo = pm.getPackageInfo(packageName, flags = PackageManager.GET_META_DATA)!!
val pluginContext = app.createPackageContext(packageName, 0)

val className = packageInfo.applicationInfo.metaData.getString(METADATA_PLUGIN_CLASS)
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
val classLoader = PathClassLoader(
packageInfo.applicationInfo.sourceDir,
Downloader::class.java.classLoader
)

val downloader = classLoader
.loadClass(className)
.getDownloaderImplementation(downloaderContext)
.getDownloaderImplementation(
DownloaderContext(
androidContext = pluginContext,
pluginHostPackageName = app.packageName
)
)

@Suppress("UNCHECKED_CAST")
DownloaderPluginState.Loaded(
LoadedDownloaderPlugin(
packageInfo.packageName,
packageName,
with(pm) { packageInfo.label() },
packageInfo.versionName,
downloader.get,
Expand All @@ -123,22 +119,22 @@ class DownloaderPluginRepository(
} catch (e: CancellationException) {
throw e
} catch (t: Throwable) {
Log.e(tag, "Failed to load plugin ${packageInfo.packageName}", t)
Log.e(tag, "Failed to load plugin $packageName", t)
DownloaderPluginState.Failed(t)
}
}

suspend fun trustPackage(packageInfo: PackageInfo) {
suspend fun trustPackage(packageName: String) {
trustDao.upsertTrust(
TrustedDownloaderPlugin(
packageInfo.packageName,
pm.getSignatures(packageInfo).first().toCharsString()
packageName,
pm.getSignature(packageName).toByteArray()
)
)

reload()
prefs.edit {
acknowledgedDownloaderPlugins += packageInfo.packageName
acknowledgedDownloaderPlugins += packageName
}
}

Expand All @@ -148,19 +144,17 @@ class DownloaderPluginRepository(
suspend fun acknowledgeAllNewPlugins() =
acknowledgedDownloaderPlugins.update(installedPluginPackageNames.value)

private suspend fun verify(packageInfo: PackageInfo): Boolean {
private suspend fun verify(packageName: String): Boolean {
val expectedSignature =
trustDao.getTrustedSignature(packageInfo.packageName)?.let(::Signature) ?: return false
trustDao.getTrustedSignature(packageName) ?: return false

return expectedSignature in pm.getSignatures(packageInfo)
return pm.hasSignature(packageName, expectedSignature)
}

private companion object {
const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader"
const val METADATA_PLUGIN_CLASS = "app.revanced.manager.plugin.downloader.class"

val packageFlags = PackageManager.GET_META_DATA or PM.signaturesFlag

val Class<*>.isDownloader get() = Downloader::class.java.isAssignableFrom(this)
const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC
val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,15 @@ fun DownloadsSettingsScreen(
val packageInfo =
remember(packageName) {
viewModel.pm.getPackageInfo(
packageName,
flags = PM.signaturesFlag
packageName
)
} ?: return@item

if (showDialog) {
val signature =
remember(packageInfo) {
remember(packageName) {
val androidSignature =
viewModel.pm.getSignatures(packageInfo).first()
viewModel.pm.getSignature(packageName)
val hash = MessageDigest.getInstance("SHA-256")
.digest(androidSignature.toByteArray())
hash.toHexString(format = HexFormat.UpperCase)
Expand Down Expand Up @@ -157,7 +156,7 @@ fun DownloadsSettingsScreen(
),
onDismiss = ::dismiss,
onConfirm = {
viewModel.trustPlugin(packageInfo)
viewModel.trustPlugin(packageName)
dismiss()
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ class DownloadsViewModel(
isRefreshingPlugins = false
}

fun trustPlugin(packageInfo: PackageInfo) = viewModelScope.launch {
downloaderPluginRepository.trustPackage(packageInfo)
fun trustPlugin(packageName: String) = viewModelScope.launch {
downloaderPluginRepository.trustPackage(packageName)
}

fun revokePluginTrust(packageName: String) = viewModelScope.launch {
Expand Down
28 changes: 13 additions & 15 deletions app/src/main/java/app/revanced/manager/util/PM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.content.pm.Signature
import android.os.Build
import android.os.Parcelable
import androidx.compose.runtime.Immutable
import androidx.core.content.pm.PackageInfoCompat
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.service.InstallService
import app.revanced.manager.service.UninstallService
Expand All @@ -37,7 +38,6 @@ data class AppInfo(
) : Parcelable

@SuppressLint("QueryPermissionsNeeded")
@Suppress("Deprecation")
class PM(
private val app: Application,
patchBundleRepository: PatchBundleRepository
Expand Down Expand Up @@ -100,8 +100,8 @@ class PM(
else
app.packageManager.getInstalledPackages(flags)

fun getPackagesWithFeature(feature: String, flags: Int = 0) =
getInstalledPackages(PackageManager.GET_CONFIGURATIONS or flags)
fun getPackagesWithFeature(feature: String) =
getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
.filter { pkg ->
pkg.reqFeatures?.any { it.name == feature } ?: false
}
Expand Down Expand Up @@ -129,15 +129,17 @@ class PM(
return pkgInfo
}

fun getSignatures(packageInfo: PackageInfo): Array<Signature> {
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
packageInfo.signingInfo.apkContentsSigners
else packageInfo.signatures
fun getSignature(packageName: String): Signature =
// Get the last signature from the list because we want the newest one if SigningInfo.getSigningCertificateHistory() was used.
PackageInfoCompat.getSignatures(app.packageManager, packageName).last()

if (signatures.isEmpty()) throw Exception("Signature information was not queried")

return signatures
}
@SuppressLint("InlinedApi")
fun hasSignature(packageName: String, signature: ByteArray) = PackageInfoCompat.hasSignatures(
app.packageManager,
packageName,
mapOf(signature to PackageManager.CERT_INPUT_RAW_X509),
false
)

fun PackageInfo.label() = this.applicationInfo.loadLabel(app.packageManager).toString()

Expand Down Expand Up @@ -196,8 +198,4 @@ class PM(
Intent(this, UninstallService::class.java),
intentFlags
).intentSender

companion object {
val signaturesFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES
}
}

0 comments on commit 886ceaf

Please sign in to comment.