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

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

import android.content.Context
import android.os.AsyncTask
import foundation.e.apps.application.model.data.FullData
23
import foundation.e.apps.utils.Constants
24
import org.bouncycastle.jce.provider.BouncyCastleProvider
Arnau Vàzquez's avatar
Arnau Vàzquez committed
25
import org.bouncycastle.openpgp.PGPCompressedData
26
27
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
import org.bouncycastle.openpgp.PGPSignatureList
Arnau Vàzquez's avatar
Arnau Vàzquez committed
28
import org.bouncycastle.openpgp.PGPUtil
29
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
Arnau Vàzquez's avatar
Arnau Vàzquez committed
30
31
32
33
34
35
36
37
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
38
39
40
41
42
43
44

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
45

46
    override fun doInBackground(vararg context: Context): Context {
47
48
        if (fullData.packageName == Constants.MICROG_PACKAGE) {
            verificationSuccessful = true
49
        } else {
50
51
52
53
54
55
56
57
58
59
60
61
            verificationSuccessful = if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
                getApkFileSha1(applicationInfo.getApkOrXapkFile(context[0], fullData, 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"))
            }
62
63
64
65
66
67
68
69
        }
        return context[0]
    }

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

70
    private fun getApkFileSha1(file: File): String? {
71
72
73
74
75
76
77
78
79
80
        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)
            }
        }
81
82
        return byteArrayToHex(messageDigest.digest())
    }
83

84
85
86
87
    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()
88
89
90
91
92
93
    }

    private fun verifyAPKSignature(
            apkInputStream: BufferedInputStream,
            apkSignatureInputStream: InputStream,
            publicKeyInputStream: InputStream): Boolean {
narinder Rana's avatar
narinder Rana committed
94
        try {
95

narinder Rana's avatar
narinder Rana committed
96
97
98
            var jcaPGPObjectFactory =
                    JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
            val pgpSignatureList: PGPSignatureList
99

narinder Rana's avatar
narinder Rana committed
100
101
102
103
104
105
106
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
107

narinder Rana's avatar
narinder Rana committed
108
109
110
111
            val pgpPublicKeyRingCollection =
                    PGPPublicKeyRingCollection(
                            PGPUtil.getDecoderStream(publicKeyInputStream),
                            JcaKeyFingerprintCalculator())
112

narinder Rana's avatar
narinder Rana committed
113
114
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
115

narinder Rana's avatar
narinder Rana committed
116
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
117

narinder Rana's avatar
narinder Rana committed
118
119
120
121
122
123
            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
124

narinder Rana's avatar
narinder Rana committed
125
126
127
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
Arnau Vàzquez's avatar
Arnau Vàzquez committed
128

narinder Rana's avatar
narinder Rana committed
129
130
131
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
132
133
            //Toast message if we want to show message to user

narinder Rana's avatar
narinder Rana committed
134
135
        }
        return false;
136
137

    }
138
}
139
140
141
142

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