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

Commit 7b658333 authored by Hasib Prince's avatar Hasib Prince
Browse files

Merge branch '5761-match_fdroid_signature' into 'main'

apk signature checking added for cleanapk

See merge request !177
parents 60fe6e5e f3b80207
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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"
+7.83 KiB

File added.

No diff preview for this file type.

+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)
        }
    }
}
+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

@@ -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?>
}
+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
@@ -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