Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 26f9ca21 authored by narinder Rana's avatar narinder Rana Committed by Aayush Gupta
Browse files

Apps: Refactor applications verification logic



The new logic uses:

- Fdroid API to verify if an application exists on it and verifies it using the signature
- Introduces a new json file to store default system applications and verifies it using their signature
- Verifies non-fdroid and non-system applications using sha1sum

Signed-off-by: Aayush Gupta's avatarAayush Gupta <theimpulson@e.email>
parent 98dd9491
Loading
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
{
  "com.explusalpha.Snes9xPlus":{
    "url" : "https://cleanapk.org/#/app/5b15b33a89bb693d3a3e806b",
    "project_id" : "1001",
    "app_name": "Snes9x EX+"
  }
}
 No newline at end of file
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021  E FOUNDATION
 * Copyright (C) 2021  ECORP SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package foundation.e.apps.api

import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Error
import java.util.*


class FDroidAppExistsRequest(private val keyword: String) {


    fun request(callback: (Error?, ArrayList<Int?>) -> Unit) {
        try {
            val l1 = ArrayList<Int?>()
            val url = Constants.F_DROID_PACKAGES_URL + keyword + "/"
            val urlConnection = Common.createConnection(url, Constants.REQUEST_METHOD_GET)
            val responseCode = urlConnection.responseCode

            urlConnection.disconnect()
            l1.add(responseCode)
            callback.invoke(null, l1)
        } catch (e: Exception) {
            callback.invoke(Error.findError(e), ArrayList())
        }
    }

}
 No newline at end of file
+45 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021  E FOUNDATION
 * Copyright (C) 2021  ECORP SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package foundation.e.apps.api

import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Error
import java.util.*


class SystemAppExistsRequest(private val keyword: String) {


    fun request(callback: (Error?, ArrayList<String?>) -> Unit) {
        try {
            val l1 = ArrayList<String?>()
            val url = Constants.SYSTEM_PACKAGES_JSON_FILE_URL
            val urlConnection = Common.createConnection(url, Constants.REQUEST_METHOD_GET)
            val data = urlConnection.inputStream.bufferedReader().readText()

            urlConnection.disconnect()
            l1.add(data)
            callback.invoke(null, l1)
        } catch (e: Exception) {
            callback.invoke(Error.findError(e), ArrayList())
        }
    }

}
 No newline at end of file
+119 −18
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2021  E FOUNDATION
 * Copyright (C) 2021 ECORP SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
@@ -18,13 +19,18 @@
package foundation.e.apps.application.model

import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.os.AsyncTask
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import foundation.e.apps.R
import foundation.e.apps.api.FDroidAppExistsRequest
import foundation.e.apps.api.SystemAppExistsRequest
import foundation.e.apps.application.model.data.FullData
import foundation.e.apps.utils.Constants
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openpgp.PGPCompressedData
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
@@ -33,6 +39,8 @@ import org.bouncycastle.openpgp.PGPUtil
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
@@ -40,36 +48,130 @@ import java.io.InputStream
import java.security.MessageDigest
import java.security.Security


class IntegrityVerificationTask(
        private val applicationInfo: ApplicationInfo,
        private val fullData: FullData,
        private val integrityVerificationCallback: IntegrityVerificationCallback) :
        AsyncTask<Context, Void, Context>() {
    private var verificationSuccessful: Boolean = false
    private var TAG = "IntegrityVerificationTask"


    override fun doInBackground(vararg context: Context): Context {
        if (fullData.packageName == Constants.MICROG_PACKAGE) {
            verificationSuccessful = true
       try {
           verificationSuccessful = if (isSystemApplication(fullData.packageName)) {
               verifySystemSignature(context[0])
           } else if (isfDroidApplication(fullData.packageName)) {
               verifyFdroidSignature(context[0])
           } else{
            verificationSuccessful = if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
                getApkFileSha1(applicationInfo.getApkOrXapkFile(context[0], fullData, fullData.basicData)) ==
               checkGoogleApp(context[0])
           }
       }catch (e: Exception){
           e.printStackTrace()
       }
        return context[0]
    }

    private fun checkGoogleApp(context: Context): Boolean {
        return verifyShaSum(context)
    }

    private fun verifyShaSum(context: Context) :Boolean {
        if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
            return getApkFileSha1(applicationInfo.getApkOrXapkFile(context, fullData, fullData.basicData)) ==
                    fullData.getLastVersion()!!.apkSHA
            } else {
        }
        return false
    }

    private fun verifySystemSignature(context: Context): Boolean {
        if (!fullData.getLastVersion()?.signature.isNullOrEmpty()) {
            return fullData.getLastVersion()?.signature ==
                    getSystemSignature(context.packageManager)?.toCharsString()
        }
        return false
    }

    private fun getFirstSignature(pkg: PackageInfo?): Signature? {
        return if (pkg?.signatures != null && pkg.signatures.isNotEmpty()) {
            pkg.signatures[0]
        } else null
    }

    private fun getSystemSignature(pm: PackageManager): Signature? {
        try {
            val sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES)
            return getFirstSignature(sys)
        } catch (e: PackageManager.NameNotFoundException) {
            Log.d(TAG, "Unable to find the package: android")
        }
        return null
    }

    private fun verifyFdroidSignature(context: Context) : Boolean {
        Security.addProvider(BouncyCastleProvider())
                verifyAPKSignature(
                        context[0],
        return verifyAPKSignature(
                context,
                BufferedInputStream(FileInputStream(
                                applicationInfo.getApkFile(context[0],
                        applicationInfo.getApkFile(context,
                                fullData.basicData).absolutePath)),
                fullData.getLastVersion()!!.signature.byteInputStream(Charsets.UTF_8),
                        context[0].assets.open("f-droid.org-signing-key.gpg"))
                context.assets.open("f-droid.org-signing-key.gpg"))
    }

    private fun isfDroidApplication(packageName: String): Boolean {
        var fDroidAppExistsResponse = 0
        FDroidAppExistsRequest(packageName)
                .request { applicationError, searchResult ->
                    when (applicationError) {
                        null -> {
                            if (searchResult.size > 0) {
                                fDroidAppExistsResponse = searchResult[0]!!
                            }
                        }
        return context[0]
                        else -> {
                            Log.e(TAG, applicationError.toString())
                        }
                    }
                }
        return fDroidAppExistsResponse == 200
    }

    private fun isSystemApplication(packageName: String): Boolean {
        var jsonResponse = ""
        SystemAppExistsRequest(packageName)
                .request { applicationError, searchResult ->
                    when (applicationError) {
                        null -> {
                            if (searchResult.size > 0) {
                                jsonResponse = searchResult[0].toString()
                            }
                        }
                        else -> {
                            Log.e(TAG, applicationError.toString())
                        }
                    }
                }
        try {
            if (packageName == JSONObject(jsonResponse).get(packageName)) {
                return true
            }
        } catch (e: Exception) {
            if (e is JSONException) {
                Log.d(TAG, "$packageName is not a system application")
            } else {
                e.printStackTrace()
            }
        }
        return false
    }

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
        if (!verificationSuccessful){
             Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
        }
    }

    private fun getApkFileSha1(file: File): String? {
@@ -136,8 +238,7 @@ class IntegrityVerificationTask(
            e.printStackTrace()

            Handler(Looper.getMainLooper()).post {
                val toast = Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed),  Toast.LENGTH_LONG)
                toast.show()
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
            }

        }
+4 −0
Original line number Diff line number Diff line
@@ -68,4 +68,8 @@ object Constants {
    const val UPDATES_NOTIFICATION_CHANNEL_TITLE = "App updates"
    const val UPDATES_NOTIFICATION_CLICK_EXTRA = "updates_notification_click_extra"

    // Integrity Verification
    const val F_DROID_PACKAGES_URL = "https://f-droid.org/en/packages/"
    const val SYSTEM_PACKAGES_JSON_FILE_URL = "https://gitlab.e.foundation/e/apps/apps/-/raw/e169c1905114d97af867b051f96c38166f4782e2/app/src/main/assets/systemApp.json"

}
Loading