use PackageInfoCompat instead of dealing with signatures manually

This commit is contained in:
Ax333l 2024-08-12 22:35:52 +02:00
parent 3f497a93d0
commit 886ceaf4b0
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
7 changed files with 56 additions and 65 deletions

View File

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "98837fd72fde0272894bce063c1095af", "identityHash": "ab113134d89f2c5e412e87775510b327",
"entities": [ "entities": [
{ {
"tableName": "patch_bundles", "tableName": "patch_bundles",
@ -405,7 +405,7 @@
}, },
{ {
"tableName": "trusted_downloader_plugins", "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": [ "fields": [
{ {
"fieldPath": "packageName", "fieldPath": "packageName",
@ -416,7 +416,7 @@
{ {
"fieldPath": "signature", "fieldPath": "signature",
"columnName": "signature", "columnName": "signature",
"affinity": "TEXT", "affinity": "BLOB",
"notNull": true "notNull": true
} }
], ],
@ -433,7 +433,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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')"
] ]
} }
} }

View File

@ -5,7 +5,7 @@ import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "trusted_downloader_plugins") @Entity(tableName = "trusted_downloader_plugins")
data class TrustedDownloaderPlugin( class TrustedDownloaderPlugin(
@PrimaryKey @ColumnInfo(name = "package_name") val packageName: String, @PrimaryKey @ColumnInfo(name = "package_name") val packageName: String,
@ColumnInfo(name = "signature") val signature: String @ColumnInfo(name = "signature") val signature: ByteArray
) )

View File

@ -8,7 +8,7 @@ import androidx.room.Upsert
@Dao @Dao
interface TrustedDownloaderPluginDao { interface TrustedDownloaderPluginDao {
@Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName") @Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName")
suspend fun getTrustedSignature(packageName: String): String? suspend fun getTrustedSignature(packageName: String): ByteArray?
@Upsert @Upsert
suspend fun upsertTrust(plugin: TrustedDownloaderPlugin) suspend fun upsertTrust(plugin: TrustedDownloaderPlugin)

View File

@ -1,9 +1,8 @@
package app.revanced.manager.domain.repository package app.revanced.manager.domain.repository
import android.content.Context import android.app.Application
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.Signature
import android.util.Log import android.util.Log
import app.revanced.manager.data.room.AppDatabase import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
@ -30,7 +29,7 @@ import java.lang.reflect.Modifier
class DownloaderPluginRepository( class DownloaderPluginRepository(
private val pm: PM, private val pm: PM,
private val prefs: PreferencesManager, private val prefs: PreferencesManager,
private val context: Context, private val app: Application,
db: AppDatabase db: AppDatabase
) { ) {
private val trustDao = db.trustedDownloaderPluginDao() private val trustDao = db.trustedDownloaderPluginDao()
@ -50,16 +49,14 @@ class DownloaderPluginRepository(
} }
suspend fun reload() { suspend fun reload() {
val pluginPackages = val plugins =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
pm.getPackagesWithFeature( pm.getPackagesWithFeature(PLUGIN_FEATURE)
PLUGIN_FEATURE, .associate { it.packageName to loadPlugin(it.packageName) }
flags = packageFlags
)
} }
_pluginStates.value = pluginPackages.associate { it.packageName to loadPlugin(it) } _pluginStates.value = plugins
installedPluginPackageNames.value = pluginPackages.map { it.packageName }.toSet() installedPluginPackageNames.value = plugins.keys
val acknowledgedPlugins = acknowledgedDownloaderPlugins.get() val acknowledgedPlugins = acknowledgedDownloaderPlugins.get()
val uninstalledPlugins = acknowledgedPlugins subtract installedPluginPackageNames.value val uninstalledPlugins = acknowledgedPlugins subtract installedPluginPackageNames.value
@ -78,28 +75,22 @@ class DownloaderPluginRepository(
return plugin to app.unwrapWith(plugin) return plugin to app.unwrapWith(plugin)
} }
private suspend fun loadPlugin(packageInfo: PackageInfo): DownloaderPluginState { private suspend fun loadPlugin(packageName: String): DownloaderPluginState {
try { try {
if (!verify(packageInfo)) return DownloaderPluginState.Untrusted if (!verify(packageName)) return DownloaderPluginState.Untrusted
} catch (e: CancellationException) { } catch (e: CancellationException) {
throw e throw e
} catch (e: Exception) { } 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) return DownloaderPluginState.Failed(e)
} }
val downloaderContext = DownloaderContext(
androidContext = context.createPackageContext(
packageInfo.packageName,
0
),
pluginHostPackageName = context.packageName
)
return try { return try {
val className = val packageInfo = pm.getPackageInfo(packageName, flags = PackageManager.GET_META_DATA)!!
packageInfo.applicationInfo.metaData.getString(METADATA_PLUGIN_CLASS) val pluginContext = app.createPackageContext(packageName, 0)
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
val className = packageInfo.applicationInfo.metaData.getString(METADATA_PLUGIN_CLASS)
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
val classLoader = PathClassLoader( val classLoader = PathClassLoader(
packageInfo.applicationInfo.sourceDir, packageInfo.applicationInfo.sourceDir,
Downloader::class.java.classLoader Downloader::class.java.classLoader
@ -107,12 +98,17 @@ class DownloaderPluginRepository(
val downloader = classLoader val downloader = classLoader
.loadClass(className) .loadClass(className)
.getDownloaderImplementation(downloaderContext) .getDownloaderImplementation(
DownloaderContext(
androidContext = pluginContext,
pluginHostPackageName = app.packageName
)
)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
DownloaderPluginState.Loaded( DownloaderPluginState.Loaded(
LoadedDownloaderPlugin( LoadedDownloaderPlugin(
packageInfo.packageName, packageName,
with(pm) { packageInfo.label() }, with(pm) { packageInfo.label() },
packageInfo.versionName, packageInfo.versionName,
downloader.get, downloader.get,
@ -123,22 +119,22 @@ class DownloaderPluginRepository(
} catch (e: CancellationException) { } catch (e: CancellationException) {
throw e throw e
} catch (t: Throwable) { } 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) DownloaderPluginState.Failed(t)
} }
} }
suspend fun trustPackage(packageInfo: PackageInfo) { suspend fun trustPackage(packageName: String) {
trustDao.upsertTrust( trustDao.upsertTrust(
TrustedDownloaderPlugin( TrustedDownloaderPlugin(
packageInfo.packageName, packageName,
pm.getSignatures(packageInfo).first().toCharsString() pm.getSignature(packageName).toByteArray()
) )
) )
reload() reload()
prefs.edit { prefs.edit {
acknowledgedDownloaderPlugins += packageInfo.packageName acknowledgedDownloaderPlugins += packageName
} }
} }
@ -148,19 +144,17 @@ class DownloaderPluginRepository(
suspend fun acknowledgeAllNewPlugins() = suspend fun acknowledgeAllNewPlugins() =
acknowledgedDownloaderPlugins.update(installedPluginPackageNames.value) acknowledgedDownloaderPlugins.update(installedPluginPackageNames.value)
private suspend fun verify(packageInfo: PackageInfo): Boolean { private suspend fun verify(packageName: String): Boolean {
val expectedSignature = 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 { private companion object {
const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader" const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader"
const val METADATA_PLUGIN_CLASS = "app.revanced.manager.plugin.downloader.class" 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) val Class<*>.isDownloader get() = Downloader::class.java.isAssignableFrom(this)
const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC
val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC

View File

@ -111,16 +111,15 @@ fun DownloadsSettingsScreen(
val packageInfo = val packageInfo =
remember(packageName) { remember(packageName) {
viewModel.pm.getPackageInfo( viewModel.pm.getPackageInfo(
packageName, packageName
flags = PM.signaturesFlag
) )
} ?: return@item } ?: return@item
if (showDialog) { if (showDialog) {
val signature = val signature =
remember(packageInfo) { remember(packageName) {
val androidSignature = val androidSignature =
viewModel.pm.getSignatures(packageInfo).first() viewModel.pm.getSignature(packageName)
val hash = MessageDigest.getInstance("SHA-256") val hash = MessageDigest.getInstance("SHA-256")
.digest(androidSignature.toByteArray()) .digest(androidSignature.toByteArray())
hash.toHexString(format = HexFormat.UpperCase) hash.toHexString(format = HexFormat.UpperCase)
@ -157,7 +156,7 @@ fun DownloadsSettingsScreen(
), ),
onDismiss = ::dismiss, onDismiss = ::dismiss,
onConfirm = { onConfirm = {
viewModel.trustPlugin(packageInfo) viewModel.trustPlugin(packageName)
dismiss() dismiss()
} }
) )

View File

@ -58,8 +58,8 @@ class DownloadsViewModel(
isRefreshingPlugins = false isRefreshingPlugins = false
} }
fun trustPlugin(packageInfo: PackageInfo) = viewModelScope.launch { fun trustPlugin(packageName: String) = viewModelScope.launch {
downloaderPluginRepository.trustPackage(packageInfo) downloaderPluginRepository.trustPackage(packageName)
} }
fun revokePluginTrust(packageName: String) = viewModelScope.launch { fun revokePluginTrust(packageName: String) = viewModelScope.launch {

View File

@ -14,6 +14,7 @@ import android.content.pm.Signature
import android.os.Build import android.os.Build
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.core.content.pm.PackageInfoCompat
import app.revanced.manager.domain.repository.PatchBundleRepository import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.service.InstallService import app.revanced.manager.service.InstallService
import app.revanced.manager.service.UninstallService import app.revanced.manager.service.UninstallService
@ -37,7 +38,6 @@ data class AppInfo(
) : Parcelable ) : Parcelable
@SuppressLint("QueryPermissionsNeeded") @SuppressLint("QueryPermissionsNeeded")
@Suppress("Deprecation")
class PM( class PM(
private val app: Application, private val app: Application,
patchBundleRepository: PatchBundleRepository patchBundleRepository: PatchBundleRepository
@ -100,8 +100,8 @@ class PM(
else else
app.packageManager.getInstalledPackages(flags) app.packageManager.getInstalledPackages(flags)
fun getPackagesWithFeature(feature: String, flags: Int = 0) = fun getPackagesWithFeature(feature: String) =
getInstalledPackages(PackageManager.GET_CONFIGURATIONS or flags) getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
.filter { pkg -> .filter { pkg ->
pkg.reqFeatures?.any { it.name == feature } ?: false pkg.reqFeatures?.any { it.name == feature } ?: false
} }
@ -129,15 +129,17 @@ class PM(
return pkgInfo return pkgInfo
} }
fun getSignatures(packageInfo: PackageInfo): Array<Signature> { fun getSignature(packageName: String): Signature =
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) // Get the last signature from the list because we want the newest one if SigningInfo.getSigningCertificateHistory() was used.
packageInfo.signingInfo.apkContentsSigners PackageInfoCompat.getSignatures(app.packageManager, packageName).last()
else packageInfo.signatures
if (signatures.isEmpty()) throw Exception("Signature information was not queried") @SuppressLint("InlinedApi")
fun hasSignature(packageName: String, signature: ByteArray) = PackageInfoCompat.hasSignatures(
return signatures app.packageManager,
} packageName,
mapOf(signature to PackageManager.CERT_INPUT_RAW_X509),
false
)
fun PackageInfo.label() = this.applicationInfo.loadLabel(app.packageManager).toString() fun PackageInfo.label() = this.applicationInfo.loadLabel(app.packageManager).toString()
@ -196,8 +198,4 @@ class PM(
Intent(this, UninstallService::class.java), Intent(this, UninstallService::class.java),
intentFlags intentFlags
).intentSender ).intentSender
companion object {
val signaturesFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES
}
} }