From 0d0982b244648d9aa02546caaa1d1d6630122071 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 19 Apr 2020 08:43:45 +0300 Subject: [PATCH] Check if in-app update allowed --- app/build.gradle | 2 +- .../kotatsu/ui/settings/AppUpdateService.kt | 67 +++++++++++++++++-- .../ui/settings/MainSettingsFragment.kt | 3 + .../koitharu/kotatsu/utils/ext/StringExt.kt | 20 ++++++ app/src/main/res/xml/pref_main.xml | 17 +++-- 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b482b7197..ad07dec78 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { minSdkVersion 21 targetSdkVersion 29 versionCode gitCommits - versionName '0.2-b1' + versionName '0.3' buildConfigField 'String', 'GIT_BRANCH', "\"${gitBranch}\"" diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt index a9e523500..2cec9ce62 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/AppUpdateService.kt @@ -1,10 +1,12 @@ package org.koitharu.kotatsu.ui.settings +import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.net.Uri import android.os.Build @@ -22,8 +24,18 @@ import org.koitharu.kotatsu.core.github.VersionId import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.ui.common.BaseService import org.koitharu.kotatsu.utils.FileSizeUtils +import org.koitharu.kotatsu.utils.ext.byte2HexFormatted +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.security.cert.CertificateEncodingException +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit + class AppUpdateService : BaseService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -92,25 +104,26 @@ class AppUpdateService : BaseService() { ) builder.setSmallIcon(R.drawable.ic_stat_update) builder.setAutoCancel(true) - builder.setColor(ContextCompat.getColor(this, R.color.blue_primary_dark)) + builder.color = ContextCompat.getColor(this, R.color.blue_primary_dark) builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)) manager.notify(NOTIFICATION_ID, builder.build()) } companion object { + private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE" private const val NOTIFICATION_ID = 202 private const val CHANNEL_ID = "update" private val PERIOD = TimeUnit.HOURS.toMillis(6) - fun start(context: Context) { - try { - context.startService(Intent(context, AppUpdateService::class.java)) - } catch (_: IllegalStateException) { - } + fun isUpdateSupported(context: Context): Boolean { + return getCertificateSHA1Fingerprint(context) == CERT_SHA1 } fun startIfRequired(context: Context) { + if (!isUpdateSupported(context)) { + return + } val settings = AppSettings(context) if (settings.appUpdateAuto) { val lastUpdate = settings.appUpdate @@ -119,5 +132,47 @@ class AppUpdateService : BaseService() { } } } + + private fun start(context: Context) { + try { + context.startService(Intent(context, AppUpdateService::class.java)) + } catch (_: IllegalStateException) { + } + } + + @Suppress("DEPRECATION") + @SuppressLint("PackageManagerGetSignatures") + private fun getCertificateSHA1Fingerprint(context: Context): String? { + val packageInfo = try { + context.packageManager.getPackageInfo( + context.packageName, + PackageManager.GET_SIGNATURES + ) + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + return null + } + val signatures = packageInfo?.signatures + val cert: ByteArray = signatures?.firstOrNull()?.toByteArray() ?: return null + val input: InputStream = ByteArrayInputStream(cert) + val c = try { + val cf = CertificateFactory.getInstance("X509") + cf.generateCertificate(input) as X509Certificate + } catch (e: CertificateException) { + e.printStackTrace() + return null + } + return try { + val md: MessageDigest = MessageDigest.getInstance("SHA1") + val publicKey: ByteArray = md.digest(c.getEncoded()) + publicKey.byte2HexFormatted() + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + null + } catch (e: CertificateEncodingException) { + e.printStackTrace() + null + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt index b5c2544ba..00f9af4e8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt @@ -37,6 +37,9 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), } findPreference(R.string.key_reader_switchers)?.summaryProvider = MultiSummaryProvider(R.string.gestures_only) + findPreference(R.string.key_app_update_auto)?.run { + isVisible = AppUpdateService.isUpdateSupported(context) + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt index b2d692162..13fe030a9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.utils.ext import android.net.Uri import java.net.URLEncoder +import java.util.* fun String.longHashCode(): Long { var h = 1125899906842597L @@ -72,4 +73,23 @@ fun String.toUriOrNull(): Uri? = if (isEmpty()) { null } else { Uri.parse(this) +} + +fun ByteArray.byte2HexFormatted(): String? { + val str = StringBuilder(size * 2) + for (i in indices) { + var h = Integer.toHexString(this[i].toInt()) + val l = h.length + if (l == 1) { + h = "0$h" + } + if (l > 2) { + h = h.substring(l - 2, l) + } + str.append(h.toUpperCase(Locale.ROOT)) + if (i < size - 1) { + str.append(':') + } + } + return str.toString() } \ No newline at end of file diff --git a/app/src/main/res/xml/pref_main.xml b/app/src/main/res/xml/pref_main.xml index 65e159f2d..4d29d019f 100644 --- a/app/src/main/res/xml/pref_main.xml +++ b/app/src/main/res/xml/pref_main.xml @@ -1,7 +1,8 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + app:iconSpaceReserved="false" + app:isPreferenceVisible="false" + tools:isPreferenceVisible="true" /> + android:key="@string/key_notifications_settings" + android:title="@string/notifications_settings" + app:iconSpaceReserved="false" />