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
Pipeline #125947 passed with stage
in 4 minutes and 2 seconds
{
"com.explusalpha.Snes9xPlus":{
"url" : "https://cleanapk.org/#/app/5b15b33a89bb693d3a3e806b",
"project_id" : "1001",
"app_name": "Snes9x EX+"
}
}
\ No newline at end of file
/*
* 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
/*
* 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
/*
* 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
} else {
verificationSuccessful = if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
getApkFileSha1(applicationInfo.getApkOrXapkFile(context[0], fullData, fullData.basicData)) ==
fullData.getLastVersion()!!.apkSHA
try {
verificationSuccessful = if (isSystemApplication(fullData.packageName)) {
verifySystemSignature(context[0])
} else if (isfDroidApplication(fullData.packageName)) {
verifyFdroidSignature(context[0])
} else{
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
}
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())
return verifyAPKSignature(
context,
BufferedInputStream(FileInputStream(
applicationInfo.getApkFile(context,
fullData.basicData).absolutePath)),
fullData.getLastVersion()!!.signature.byteInputStream(Charsets.UTF_8),
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]!!
}
}
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 {
Security.addProvider(BouncyCastleProvider())
verifyAPKSignature(
context[0],
BufferedInputStream(FileInputStream(
applicationInfo.getApkFile(context[0],
fullData.basicData).absolutePath)),
fullData.getLastVersion()!!.signature.byteInputStream(Charsets.UTF_8),
context[0].assets.open("f-droid.org-signing-key.gpg"))
e.printStackTrace()
}
}
return context[0]
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()
}
}
......
......@@ -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"
}
......@@ -161,6 +161,7 @@
<string name="error_incident">An error occured</string>
<string name="image_carousel">Image carousel</string>
<string name="divider">Divider</string>
<string name="not_able_install">Not able to install this application! verification failed </string>
</resources>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment