From 886ceaf4b0fb965410a019577ae4e0542465a049 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Mon, 12 Aug 2024 22:35:52 +0200 Subject: [PATCH] use PackageInfoCompat instead of dealing with signatures manually --- .../1.json | 8 +-- .../room/plugins/TrustedDownloaderPlugin.kt | 4 +- .../plugins/TrustedDownloaderPluginDao.kt | 2 +- .../repository/DownloaderPluginRepository.kt | 66 +++++++++---------- .../settings/DownloadsSettingsScreen.kt | 9 ++- .../ui/viewmodel/DownloadsViewModel.kt | 4 +- .../main/java/app/revanced/manager/util/PM.kt | 28 ++++---- 7 files changed, 56 insertions(+), 65 deletions(-) diff --git a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json index e99d8430b7..ce36924a5f 100644 --- a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json +++ b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "98837fd72fde0272894bce063c1095af", + "identityHash": "ab113134d89f2c5e412e87775510b327", "entities": [ { "tableName": "patch_bundles", @@ -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", @@ -416,7 +416,7 @@ { "fieldPath": "signature", "columnName": "signature", - "affinity": "TEXT", + "affinity": "BLOB", "notNull": true } ], @@ -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')" ] } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPlugin.kt b/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPlugin.kt index 0d97712bd2..8e1b9c39bd 100644 --- a/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPlugin.kt +++ b/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPlugin.kt @@ -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 ) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPluginDao.kt b/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPluginDao.kt index f04bd4f575..ad1845f73d 100644 --- a/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPluginDao.kt +++ b/app/src/main/java/app/revanced/manager/data/room/plugins/TrustedDownloaderPluginDao.kt @@ -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) diff --git a/app/src/main/java/app/revanced/manager/domain/repository/DownloaderPluginRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/DownloaderPluginRepository.kt index 2861a9ec40..20bbfb55bd 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/DownloaderPluginRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/DownloaderPluginRepository.kt @@ -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 @@ -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() @@ -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 @@ -78,28 +75,22 @@ 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 @@ -107,12 +98,17 @@ class DownloaderPluginRepository( 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, @@ -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 } } @@ -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 diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt index 3720e3a8f4..56f4e3d240 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt @@ -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) @@ -157,7 +156,7 @@ fun DownloadsSettingsScreen( ), onDismiss = ::dismiss, onConfirm = { - viewModel.trustPlugin(packageInfo) + viewModel.trustPlugin(packageName) dismiss() } ) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/DownloadsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/DownloadsViewModel.kt index b945ba812e..e2c750df8d 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/DownloadsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/DownloadsViewModel.kt @@ -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 { diff --git a/app/src/main/java/app/revanced/manager/util/PM.kt b/app/src/main/java/app/revanced/manager/util/PM.kt index 340221ad3d..f275c5e76b 100644 --- a/app/src/main/java/app/revanced/manager/util/PM.kt +++ b/app/src/main/java/app/revanced/manager/util/PM.kt @@ -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 @@ -37,7 +38,6 @@ data class AppInfo( ) : Parcelable @SuppressLint("QueryPermissionsNeeded") -@Suppress("Deprecation") class PM( private val app: Application, patchBundleRepository: PatchBundleRepository @@ -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 } @@ -129,15 +129,17 @@ class PM( return pkgInfo } - fun getSignatures(packageInfo: PackageInfo): Array { - 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() @@ -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 - } } \ No newline at end of file