Loading app/build.gradle +3 −1 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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' } app/src/main/assets/f-droid.org-signing-key.gpg 0 → 100644 +7.83 KiB File added.No diff preview for this file type. View file app/src/main/java/foundation/e/apps/application/ApplicationActivity.kt +0 −8 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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 Loading app/src/main/java/foundation/e/apps/application/model/Downloader.kt +16 −22 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading app/src/main/java/foundation/e/apps/application/model/IntegrityVerificationTask.kt 0 → 100644 +101 −0 Original line number Diff line number Diff line 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) } Loading
app/build.gradle +3 −1 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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' }
app/src/main/assets/f-droid.org-signing-key.gpg 0 → 100644 +7.83 KiB File added.No diff preview for this file type. View file
app/src/main/java/foundation/e/apps/application/ApplicationActivity.kt +0 −8 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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 Loading
app/src/main/java/foundation/e/apps/application/model/Downloader.kt +16 −22 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading
app/src/main/java/foundation/e/apps/application/model/IntegrityVerificationTask.kt 0 → 100644 +101 −0 Original line number Diff line number Diff line 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) }