Commit df8bd306 authored by Nihar Thakkar's avatar Nihar Thakkar
Browse files

Verify APK integrity, minor UI improvements

parent b007795d
......@@ -27,7 +27,7 @@ android {
dependencies {
def lifecycle_version = "1.1.1"
def work_version = "1.0.0-beta01"
def work_version = "1.0.0-beta04"
implementation "android.arch.work:work-runtime:$work_version"
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
......@@ -45,4 +45,6 @@ dependencies {
implementation files('libs/jackson-annotations-2.9.7.jar')
implementation files('libs/jackson-core-2.9.7.jar')
implementation group: 'commons-codec', name: 'commons-codec', version: '1.11'
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
implementation 'org.bouncycastle:bcpg-jdk15on:1.60'
}
......@@ -450,16 +450,12 @@ class ApplicationActivity : AppCompatActivity(), ApplicationStateListener,
app_install.text = resources.getString(state.installButtonTextId)
when (state) {
State.INSTALLED -> {
app_install.setBackgroundResource(R.drawable.app_install_border)
app_install.setTextColor(resources.getColor(android.R.color.primary_text_dark))
app_install.isEnabled =
Common.appHasLaunchActivity(this, application.packageName)
app_size.visibility = View.VISIBLE
app_download_container.visibility = View.GONE
}
State.DOWNLOADING -> {
app_install.setBackgroundResource(R.drawable.app_install_border_simple)
app_install.setTextColor(resources.getColor(android.R.color.primary_text_light))
app_install.isEnabled = true
app_size.visibility = View.GONE
app_download_mb.text = getString(R.string.state_installing)
......@@ -468,15 +464,11 @@ class ApplicationActivity : AppCompatActivity(), ApplicationStateListener,
app_download_container.visibility = View.VISIBLE
}
State.INSTALLING -> {
app_install.setBackgroundResource(R.drawable.app_install_border)
app_install.setTextColor(resources.getColor(android.R.color.primary_text_dark))
app_install.isEnabled = false
app_size.visibility = View.VISIBLE
app_download_container.visibility = View.GONE
}
else -> {
app_install.setBackgroundResource(R.drawable.app_install_border)
app_install.setTextColor(resources.getColor(android.R.color.primary_text_dark))
app_install.isEnabled = true
app_size.visibility = View.VISIBLE
app_download_container.visibility = View.GONE
......
......@@ -10,14 +10,11 @@ import foundation.e.apps.utils.Constants
import android.content.Intent
import android.content.BroadcastReceiver
import android.content.IntentFilter
import org.apache.commons.codec.binary.Hex
import java.io.File
import java.io.FileInputStream
import java.lang.Exception
import java.security.MessageDigest
import android.os.AsyncTask
class Downloader(private val applicationInfo: ApplicationInfo, private val fullData: FullData,
private val downloaderInterface: DownloaderInterface) {
private val downloaderInterface: DownloaderInterface) :
IntegrityVerificationCallback {
private lateinit var downloadManager: DownloadManager
private lateinit var request: DownloadManager.Request
private var downloadId: Long = 0
......@@ -103,33 +100,30 @@ class Downloader(private val applicationInfo: ApplicationInfo, private val fullD
downloadManager.remove(downloadId)
}
@Throws(Exception::class)
private fun getApkFileSha1(file: File): String {
val messageDigest = MessageDigest.getInstance("SHA-1")
val fileInputStream = FileInputStream(file)
var length = 0
val buffer = ByteArray(8192)
while (length != -1) {
length = fileInputStream.read(buffer)
if (length > 0) {
messageDigest.update(buffer, 0, length)
}
}
return String(Hex.encodeHex(messageDigest.digest()))
}
private var onComplete: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
unregisterReceivers(context)
val status = getDownloadStatus()
if (status != null && status == DownloadManager.STATUS_SUCCESSFUL) {
downloaderInterface.onDownloadComplete(context, DownloadManager.STATUS_SUCCESSFUL)
IntegrityVerificationTask(
applicationInfo,
fullData,
this@Downloader)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, context)
} else {
downloaderInterface.onDownloadComplete(context, DownloadManager.STATUS_FAILED)
}
}
}
override fun onIntegrityVerified(context: Context, verificationSuccessful: Boolean) {
if (verificationSuccessful) {
downloaderInterface.onDownloadComplete(context, DownloadManager.STATUS_SUCCESSFUL)
} else {
downloaderInterface.onDownloadComplete(context, DownloadManager.STATUS_FAILED)
}
}
private fun getDownloadStatus(): Int? {
val query = DownloadManager.Query().apply {
setFilterById(downloadId)
......
package foundation.e.apps.application.model
import android.content.Context
import android.os.AsyncTask
import foundation.e.apps.application.model.data.FullData
import org.apache.commons.codec.binary.Hex
import java.security.MessageDigest
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.Security
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
import org.bouncycastle.openpgp.PGPUtil
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
import org.bouncycastle.openpgp.PGPSignatureList
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
import org.bouncycastle.openpgp.PGPCompressedData
import java.io.*
class IntegrityVerificationTask(
private val applicationInfo: ApplicationInfo,
private val fullData: FullData,
private val integrityVerificationCallback: IntegrityVerificationCallback) :
AsyncTask<Context, Void, Context>() {
private var verificationSuccessful: Boolean = false
override fun doInBackground(vararg context: Context): Context {
verificationSuccessful = if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
getApkFileSha1(applicationInfo.getApkFile(context[0], fullData.basicData)) ==
fullData.getLastVersion()!!.apkSHA
} else {
Security.addProvider(BouncyCastleProvider())
verifyAPKSignature(
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"))
}
return context[0]
}
override fun onPostExecute(context: Context) {
integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
}
private fun getApkFileSha1(file: File): String {
val messageDigest = MessageDigest.getInstance("SHA-1")
val fileInputStream = FileInputStream(file)
var length = 0
val buffer = ByteArray(8192)
while (length != -1) {
length = fileInputStream.read(buffer)
if (length > 0) {
messageDigest.update(buffer, 0, length)
}
}
return String(Hex.encodeHex(messageDigest.digest()))
}
private fun verifyAPKSignature(
apkInputStream: BufferedInputStream,
apkSignatureInputStream: InputStream,
publicKeyInputStream: InputStream): Boolean {
var jcaPGPObjectFactory =
JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
val pgpSignatureList: PGPSignatureList
val pgpObject = jcaPGPObjectFactory.nextObject()
if (pgpObject is PGPCompressedData) {
jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
} else {
pgpSignatureList = pgpObject as PGPSignatureList
}
val pgpPublicKeyRingCollection =
PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(publicKeyInputStream),
JcaKeyFingerprintCalculator())
val signature = pgpSignatureList.get(0)
val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
signature.init(JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key)
var character = apkInputStream.read()
while (character >= 0) {
signature.update(character.toByte())
character = apkInputStream.read()
}
apkInputStream.close()
apkSignatureInputStream.close()
publicKeyInputStream.close()
return signature.verify()
}
}
interface IntegrityVerificationCallback {
fun onIntegrityVerified(context: Context, verificationSuccessful: Boolean)
}
Supports Markdown
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