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

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
47

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

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

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

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

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

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

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

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

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

narinder Rana's avatar
narinder Rana committed
120
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
121

narinder Rana's avatar
narinder Rana committed
122
123
124
125
126
127
            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
128

narinder Rana's avatar
narinder Rana committed
129
130
131
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
narinder Rana's avatar
narinder Rana committed
132
            Toast.makeText(context, R.string.Signature_verification_failed, Toast.LENGTH_LONG).show();
narinder Rana's avatar
narinder Rana committed
133
134
135
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
136
            //Toast message if we want to show message to user
narinder Rana's avatar
narinder Rana committed
137
            Toast.makeText(context, R.string.Signature_verification_failed, Toast.LENGTH_LONG).show();
narinder Rana's avatar
narinder Rana committed
138
139
        }
        return false;
140
141

    }
142
}
143
144
145
146

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