IntegrityVerificationTask.kt 5.79 KB
Newer Older
1
/*
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 * Copyright (C) 2019-2021  E FOUNDATION
 *
 * 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/>.
16
17
 */

18
19
20
21
package foundation.e.apps.application.model

import android.content.Context
import android.os.AsyncTask
narinder Rana's avatar
narinder Rana committed
22
23
import android.os.Handler
import android.os.Looper
narinder Rana's avatar
narinder Rana committed
24
25
import android.widget.Toast
import foundation.e.apps.R
26
import foundation.e.apps.application.model.data.FullData
27
import foundation.e.apps.utils.Constants
28
import org.bouncycastle.jce.provider.BouncyCastleProvider
Arnau Vàzquez's avatar
Arnau Vàzquez committed
29
import org.bouncycastle.openpgp.PGPCompressedData
30
31
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
import org.bouncycastle.openpgp.PGPSignatureList
Arnau Vàzquez's avatar
Arnau Vàzquez committed
32
import org.bouncycastle.openpgp.PGPUtil
33
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
Arnau Vàzquez's avatar
Arnau Vàzquez committed
34
35
36
37
38
39
40
41
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.Security
42
43
44
45
46
47
48

class IntegrityVerificationTask(
        private val applicationInfo: ApplicationInfo,
        private val fullData: FullData,
        private val integrityVerificationCallback: IntegrityVerificationCallback) :
        AsyncTask<Context, Void, Context>() {
    private var verificationSuccessful: Boolean = false
Arnau Vàzquez's avatar
Arnau Vàzquez committed
49

50
    override fun doInBackground(vararg context: Context): Context {
51
52
        if (fullData.packageName == Constants.MICROG_PACKAGE) {
            verificationSuccessful = true
53
        } else {
54
55
56
57
58
59
            verificationSuccessful = if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
                getApkFileSha1(applicationInfo.getApkOrXapkFile(context[0], fullData, fullData.basicData)) ==
                        fullData.getLastVersion()!!.apkSHA
            } else {
                Security.addProvider(BouncyCastleProvider())
                verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
60
                        context[0],
61
62
63
64
65
66
                        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"))
            }
67
68
69
70
71
72
73
74
        }
        return context[0]
    }

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
    }

75
    private fun getApkFileSha1(file: File): String? {
76
77
78
79
80
81
82
83
84
85
        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)
            }
        }
86
87
        return byteArrayToHex(messageDigest.digest())
    }
88

89
90
91
92
    private fun byteArrayToHex(a: ByteArray): String? {
        val sb = StringBuilder(a.size * 2)
        for (b in a) sb.append(String.format("%02x", b))
        return sb.toString()
93
94
95
    }

    private fun verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
96
            context: Context,
97
98
99
            apkInputStream: BufferedInputStream,
            apkSignatureInputStream: InputStream,
            publicKeyInputStream: InputStream): Boolean {
narinder Rana's avatar
narinder Rana committed
100
        try {
101

narinder Rana's avatar
narinder Rana committed
102
103
104
            var jcaPGPObjectFactory =
                    JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
            val pgpSignatureList: PGPSignatureList
105

narinder Rana's avatar
narinder Rana committed
106
107
108
109
110
111
112
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
113

narinder Rana's avatar
narinder Rana committed
114
115
116
117
            val pgpPublicKeyRingCollection =
                    PGPPublicKeyRingCollection(
                            PGPUtil.getDecoderStream(publicKeyInputStream),
                            JcaKeyFingerprintCalculator())
118

narinder Rana's avatar
narinder Rana committed
119
120
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
121

narinder Rana's avatar
narinder Rana committed
122
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
123

narinder Rana's avatar
narinder Rana committed
124
125
126
127
128
129
            val buff = ByteArray(1024)
            var read = apkInputStream.read(buff)
            while (read != -1) {
                signature.update(buff, 0, read)
                read = apkInputStream.read(buff)
            }
Arnau Vàzquez's avatar
Arnau Vàzquez committed
130

narinder Rana's avatar
narinder Rana committed
131
132
133
134
135
136
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
137
138
139
140
141
142

            Handler(Looper.getMainLooper()).post {
                val toast = Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed),  Toast.LENGTH_LONG)
                toast.show()
            }

narinder Rana's avatar
narinder Rana committed
143
        }
narinder Rana's avatar
narinder Rana committed
144

narinder Rana's avatar
narinder Rana committed
145
        return false;
146
147

    }
148
}
149
150
151
152

interface IntegrityVerificationCallback {
    fun onIntegrityVerified(context: Context, verificationSuccessful: Boolean)
}