From d9c0b2d836ee2d1656c01ad0ca14b544df79fa86 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 27 Nov 2023 09:08:53 +0530 Subject: [PATCH 1/3] Improve app signature check --- .../schabi/newpipe/util/ReleaseVersionUtil.kt | 88 ++++--------------- 1 file changed, 15 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index 5a54b29d224..fc31a4d9432 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -1,97 +1,39 @@ package org.schabi.newpipe.util import android.content.pm.PackageManager -import android.content.pm.Signature import androidx.core.content.pm.PackageInfoCompat import org.schabi.newpipe.App import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification import org.schabi.newpipe.error.UserAction -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.time.Instant import java.time.ZonedDateTime import java.time.format.DateTimeFormatter object ReleaseVersionUtil { // Public key of the certificate that is used in NewPipe release versions - private const val RELEASE_CERT_PUBLIC_KEY_SHA1 = - "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15" + private const val RELEASE_CERT_PUBLIC_KEY_SHA256 = + "cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab" @JvmStatic fun isReleaseApk(): Boolean { - return certificateSHA1Fingerprint == RELEASE_CERT_PUBLIC_KEY_SHA1 - } - - /** - * Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133. - * - * @return String with the APK's SHA1 fingerprint in hexadecimal - */ - private val certificateSHA1Fingerprint: String - get() { - val app = App.getApp() - val signatures: List = try { - PackageInfoCompat.getSignatures(app.packageManager, app.packageName) - } catch (e: PackageManager.NameNotFoundException) { - showRequestError(app, e, "Could not find package info") - return "" - } - if (signatures.isEmpty()) { - return "" - } - val x509cert = try { - val cf = CertificateFactory.getInstance("X509") - cf.generateCertificate(signatures[0].toByteArray().inputStream()) as X509Certificate - } catch (e: CertificateException) { - showRequestError(app, e, "Certificate error") - return "" - } - - return try { - val md = MessageDigest.getInstance("SHA1") - val publicKey = md.digest(x509cert.encoded) - byte2HexFormatted(publicKey) - } catch (e: NoSuchAlgorithmException) { - showRequestError(app, e, "Could not retrieve SHA1 key") - "" - } catch (e: CertificateEncodingException) { - showRequestError(app, e, "Could not retrieve SHA1 key") - "" - } - } - - private fun byte2HexFormatted(arr: ByteArray): String { - val str = StringBuilder(arr.size * 2) - for (i in arr.indices) { - var h = Integer.toHexString(arr[i].toInt()) - val l = h.length - if (l == 1) { - h = "0$h" - } - if (l > 2) { - h = h.substring(l - 2, l) - } - str.append(h.uppercase()) - if (i < arr.size - 1) { - str.append(':') - } - } - return str.toString() - } - - private fun showRequestError(app: App, e: Exception, request: String) { - createNotification( - app, ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, request) + @Suppress("NewApi") + val certificates = mapOf( + RELEASE_CERT_PUBLIC_KEY_SHA256.toByteArray() to PackageManager.CERT_INPUT_SHA256 ) + val app = App.getApp() + return try { + PackageInfoCompat.hasSignatures(app.packageManager, app.packageName, certificates, false) + } catch (e: PackageManager.NameNotFoundException) { + createNotification( + app, ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info") + ) + false + } } fun isLastUpdateCheckExpired(expiry: Long): Boolean { - return Instant.ofEpochSecond(expiry).isBefore(Instant.now()) + return Instant.ofEpochSecond(expiry) < Instant.now() } /** From 4ae937634e5ebc27fc0e02961f226a786cb41804 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Tue, 6 Feb 2024 05:11:13 +0530 Subject: [PATCH 2/3] Convert isReleaseApk to lazy value --- .../main/java/org/schabi/newpipe/NewVersionWorker.kt | 10 ++++------ .../schabi/newpipe/settings/MainSettingsFragment.java | 2 +- .../org/schabi/newpipe/settings/SettingsActivity.java | 2 +- .../java/org/schabi/newpipe/util/ReleaseVersionUtil.kt | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt b/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt index 39d8e90dcdc..000b83953ec 100644 --- a/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt @@ -20,9 +20,7 @@ import com.grack.nanojson.JsonParser import com.grack.nanojson.JsonParserException import org.schabi.newpipe.extractor.downloader.Response import org.schabi.newpipe.extractor.exceptions.ReCaptchaException -import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry -import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired -import org.schabi.newpipe.util.ReleaseVersionUtil.isReleaseApk +import org.schabi.newpipe.util.ReleaseVersionUtil import java.io.IOException class NewVersionWorker( @@ -84,7 +82,7 @@ class NewVersionWorker( @Throws(IOException::class, ReCaptchaException::class) private fun checkNewVersion() { // Check if the current apk is a github one or not. - if (!isReleaseApk()) { + if (!ReleaseVersionUtil.isReleaseApk) { return } @@ -93,7 +91,7 @@ class NewVersionWorker( // Check if the last request has happened a certain time ago // to reduce the number of API requests. val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0) - if (!isLastUpdateCheckExpired(expiry)) { + if (!ReleaseVersionUtil.isLastUpdateCheckExpired(expiry)) { return } } @@ -108,7 +106,7 @@ class NewVersionWorker( try { // Store a timestamp which needs to be exceeded, // before a new request to the API is made. - val newExpiry = coerceUpdateCheckExpiry(response.getHeader("expires")) + val newExpiry = ReleaseVersionUtil.coerceUpdateCheckExpiry(response.getHeader("expires")) prefs.edit { putLong(applicationContext.getString(R.string.update_expiry_key), newExpiry) } diff --git a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java index 3776d78f679..32e33d55bf6 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java @@ -23,7 +23,7 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called // Check if the app is updatable - if (!ReleaseVersionUtil.isReleaseApk()) { + if (!ReleaseVersionUtil.INSTANCE.isReleaseApk()) { getPreferenceScreen().removePreference( findPreference(getString(R.string.update_pref_screen_key))); diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index 3ee6668bf94..529e5344220 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -266,7 +266,7 @@ private void initSearch( */ private void ensureSearchRepresentsApplicationState() { // Check if the update settings are available - if (!ReleaseVersionUtil.isReleaseApk()) { + if (!ReleaseVersionUtil.INSTANCE.isReleaseApk()) { SettingsResourceRegistry.getInstance() .getEntryByPreferencesResId(R.xml.update_settings) .setSearchable(false); diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index fc31a4d9432..fc1a7d8cc2d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -15,14 +15,13 @@ object ReleaseVersionUtil { private const val RELEASE_CERT_PUBLIC_KEY_SHA256 = "cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab" - @JvmStatic - fun isReleaseApk(): Boolean { + val isReleaseApk by lazy { @Suppress("NewApi") val certificates = mapOf( RELEASE_CERT_PUBLIC_KEY_SHA256.toByteArray() to PackageManager.CERT_INPUT_SHA256 ) val app = App.getApp() - return try { + try { PackageInfoCompat.hasSignatures(app.packageManager, app.packageName, certificates, false) } catch (e: PackageManager.NameNotFoundException) { createNotification( From 7499ce6dc1ec1b55773ffc4b3a004ca1898fcb2e Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Mar 2024 06:49:09 +0530 Subject: [PATCH 3/3] Use hexToByteArray() extension --- .../main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index fc1a7d8cc2d..3ea19fa4f8b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -15,10 +15,11 @@ object ReleaseVersionUtil { private const val RELEASE_CERT_PUBLIC_KEY_SHA256 = "cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab" + @OptIn(ExperimentalStdlibApi::class) val isReleaseApk by lazy { @Suppress("NewApi") val certificates = mapOf( - RELEASE_CERT_PUBLIC_KEY_SHA256.toByteArray() to PackageManager.CERT_INPUT_SHA256 + RELEASE_CERT_PUBLIC_KEY_SHA256.hexToByteArray() to PackageManager.CERT_INPUT_SHA256 ) val app = App.getApp() try {