Loading app/build.gradle +3 −0 Original line number Diff line number Diff line Loading @@ -153,6 +153,9 @@ dependencies { //logger implementation 'com.jakewharton.timber:timber:5.0.1' // Bouncy Castle implementation 'org.bouncycastle:bcpg-jdk15on:1.60' // Retrofit def retrofit_version = "2.9.0" implementation "com.squareup.retrofit2:retrofit:$retrofit_version" Loading 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/api/cleanapk/ApkSignatureManager.kt 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 ECORP * * 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.cleanapk import android.content.Context import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openpgp.PGPCompressedData import org.bouncycastle.openpgp.PGPPublicKeyRingCollection import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignatureList 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 java.io.BufferedInputStream import java.io.FileInputStream import java.io.InputStream import java.security.Security object ApkSignatureManager { fun verifyFdroidSignature(context: Context, apkFilePath: String, signature: String): Boolean { Security.addProvider(BouncyCastleProvider()) return verifyAPKSignature( BufferedInputStream(FileInputStream(apkFilePath)), signature.byteInputStream(Charsets.UTF_8), context.assets.open("f-droid.org-signing-key.gpg") ) } private fun verifyAPKSignature( apkInputStream: BufferedInputStream, apkSignatureInputStream: InputStream, publicKeyInputStream: InputStream ): Boolean { try { val signature = extractSignature(apkSignatureInputStream) val pgpPublicKeyRingCollection = PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(publicKeyInputStream), JcaKeyFingerprintCalculator() ) val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID) signature.init(BcPGPContentVerifierBuilderProvider(), key) updateSignature(apkInputStream, signature) return signature.verify() } catch (e: Exception) { e.printStackTrace() } finally { apkInputStream.close() apkSignatureInputStream.close() publicKeyInputStream.close() } return false } private fun extractSignature(apkSignatureInputStream: InputStream): PGPSignature { 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 signature = pgpSignatureList.get(0) return signature } private fun updateSignature( apkInputStream: BufferedInputStream, signature: PGPSignature ) { val buff = ByteArray(1024) var read = apkInputStream.read(buff) while (read != -1) { signature.update(buff, 0, read) read = apkInputStream.read(buff) } } } app/src/main/java/foundation/e/apps/api/fdroid/FdroidApiInterface.kt +2 −1 Original line number Diff line number Diff line package foundation.e.apps.api.fdroid import foundation.e.apps.api.fdroid.models.FdroidApiModel import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path Loading @@ -15,5 +16,5 @@ interface FdroidApiInterface { } @GET("{packageName}.yml") suspend fun getFdroidInfoForPackage(@Path("packageName") packageName: String): FdroidApiModel? suspend fun getFdroidInfoForPackage(@Path("packageName") packageName: String): Response<FdroidApiModel?> } app/src/main/java/foundation/e/apps/api/fdroid/FdroidRepository.kt +14 −1 Original line number Diff line number Diff line package foundation.e.apps.api.fdroid import android.content.Context import foundation.e.apps.api.cleanapk.ApkSignatureManager import foundation.e.apps.api.fdroid.models.FdroidEntity import javax.inject.Inject import javax.inject.Singleton Loading @@ -18,10 +20,21 @@ class FdroidRepository @Inject constructor( */ suspend fun getFdroidInfo(packageName: String): FdroidEntity? { return fdroidDao.getFdroidEntityFromPackageName(packageName) ?: fdroidApi.getFdroidInfoForPackage(packageName)?.let { ?: fdroidApi.getFdroidInfoForPackage(packageName).body()?.let { FdroidEntity(packageName, it.authorName).also { fdroidDao.saveFdroidEntity(it) } } } suspend fun isFdroidApplicationSigned(context: Context, packageName: String, apkFilePath: String, signature: String): Boolean { if (isFdroidApplication(packageName)) { return ApkSignatureManager.verifyFdroidSignature(context, apkFilePath, signature) } return false } private suspend fun isFdroidApplication(packageName: String): Boolean { return fdroidApi.getFdroidInfoForPackage(packageName).isSuccessful } } Loading
app/build.gradle +3 −0 Original line number Diff line number Diff line Loading @@ -153,6 +153,9 @@ dependencies { //logger implementation 'com.jakewharton.timber:timber:5.0.1' // Bouncy Castle implementation 'org.bouncycastle:bcpg-jdk15on:1.60' // Retrofit def retrofit_version = "2.9.0" implementation "com.squareup.retrofit2:retrofit:$retrofit_version" Loading
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/api/cleanapk/ApkSignatureManager.kt 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 ECORP * * 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.cleanapk import android.content.Context import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openpgp.PGPCompressedData import org.bouncycastle.openpgp.PGPPublicKeyRingCollection import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignatureList 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 java.io.BufferedInputStream import java.io.FileInputStream import java.io.InputStream import java.security.Security object ApkSignatureManager { fun verifyFdroidSignature(context: Context, apkFilePath: String, signature: String): Boolean { Security.addProvider(BouncyCastleProvider()) return verifyAPKSignature( BufferedInputStream(FileInputStream(apkFilePath)), signature.byteInputStream(Charsets.UTF_8), context.assets.open("f-droid.org-signing-key.gpg") ) } private fun verifyAPKSignature( apkInputStream: BufferedInputStream, apkSignatureInputStream: InputStream, publicKeyInputStream: InputStream ): Boolean { try { val signature = extractSignature(apkSignatureInputStream) val pgpPublicKeyRingCollection = PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(publicKeyInputStream), JcaKeyFingerprintCalculator() ) val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID) signature.init(BcPGPContentVerifierBuilderProvider(), key) updateSignature(apkInputStream, signature) return signature.verify() } catch (e: Exception) { e.printStackTrace() } finally { apkInputStream.close() apkSignatureInputStream.close() publicKeyInputStream.close() } return false } private fun extractSignature(apkSignatureInputStream: InputStream): PGPSignature { 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 signature = pgpSignatureList.get(0) return signature } private fun updateSignature( apkInputStream: BufferedInputStream, signature: PGPSignature ) { val buff = ByteArray(1024) var read = apkInputStream.read(buff) while (read != -1) { signature.update(buff, 0, read) read = apkInputStream.read(buff) } } }
app/src/main/java/foundation/e/apps/api/fdroid/FdroidApiInterface.kt +2 −1 Original line number Diff line number Diff line package foundation.e.apps.api.fdroid import foundation.e.apps.api.fdroid.models.FdroidApiModel import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path Loading @@ -15,5 +16,5 @@ interface FdroidApiInterface { } @GET("{packageName}.yml") suspend fun getFdroidInfoForPackage(@Path("packageName") packageName: String): FdroidApiModel? suspend fun getFdroidInfoForPackage(@Path("packageName") packageName: String): Response<FdroidApiModel?> }
app/src/main/java/foundation/e/apps/api/fdroid/FdroidRepository.kt +14 −1 Original line number Diff line number Diff line package foundation.e.apps.api.fdroid import android.content.Context import foundation.e.apps.api.cleanapk.ApkSignatureManager import foundation.e.apps.api.fdroid.models.FdroidEntity import javax.inject.Inject import javax.inject.Singleton Loading @@ -18,10 +20,21 @@ class FdroidRepository @Inject constructor( */ suspend fun getFdroidInfo(packageName: String): FdroidEntity? { return fdroidDao.getFdroidEntityFromPackageName(packageName) ?: fdroidApi.getFdroidInfoForPackage(packageName)?.let { ?: fdroidApi.getFdroidInfoForPackage(packageName).body()?.let { FdroidEntity(packageName, it.authorName).also { fdroidDao.saveFdroidEntity(it) } } } suspend fun isFdroidApplicationSigned(context: Context, packageName: String, apkFilePath: String, signature: String): Boolean { if (isFdroidApplication(packageName)) { return ApkSignatureManager.verifyFdroidSignature(context, apkFilePath, signature) } return false } private suspend fun isFdroidApplication(packageName: String): Boolean { return fdroidApi.getFdroidInfoForPackage(packageName).isSuccessful } }