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

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

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

class IntegrityVerificationTask(
Aayush Gupta's avatar
Aayush Gupta committed
52
53
54
55
56
    private val applicationInfo: ApplicationInfo,
    private val fullData: FullData,
    private val integrityVerificationCallback: IntegrityVerificationCallback
) :
    AsyncTask<Context, Void, Context>() {
57
    private var verificationSuccessful: Boolean = false
58
59
    private var TAG = "IntegrityVerificationTask"

60
    override fun doInBackground(vararg context: Context): Context {
Aayush Gupta's avatar
Aayush Gupta committed
61
62
63
64
65
66
67
68
69
70
71
        try {
            verificationSuccessful = if (isSystemApplication(fullData.packageName)) {
                verifySystemSignature(context[0])
            } else if (isfDroidApplication(fullData.packageName)) {
                verifyFdroidSignature(context[0])
            } else {
                checkGoogleApp(context[0])
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
72
73
74
75
76
77
78
        return context[0]
    }

    private fun checkGoogleApp(context: Context): Boolean {
        return verifyShaSum(context)
    }

Aayush Gupta's avatar
Aayush Gupta committed
79
    private fun verifyShaSum(context: Context): Boolean {
80
81
        if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
            return getApkFileSha1(applicationInfo.getApkOrXapkFile(context, fullData, fullData.basicData)) ==
Aayush Gupta's avatar
Aayush Gupta committed
82
                fullData.getLastVersion()!!.apkSHA
83
84
85
86
87
88
89
        }
        return false
    }

    private fun verifySystemSignature(context: Context): Boolean {
        if (!fullData.getLastVersion()?.signature.isNullOrEmpty()) {
            return fullData.getLastVersion()?.signature ==
Aayush Gupta's avatar
Aayush Gupta committed
90
                getSystemSignature(context.packageManager)?.toCharsString()
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        }
        return false
    }

    private fun getFirstSignature(pkg: PackageInfo?): Signature? {
        return if (pkg?.signatures != null && pkg.signatures.isNotEmpty()) {
            pkg.signatures[0]
        } else null
    }

    private fun getSystemSignature(pm: PackageManager): Signature? {
        try {
            val sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES)
            return getFirstSignature(sys)
        } catch (e: PackageManager.NameNotFoundException) {
            Log.d(TAG, "Unable to find the package: android")
        }
        return null
    }

Aayush Gupta's avatar
Aayush Gupta committed
111
    private fun verifyFdroidSignature(context: Context): Boolean {
112
113
        Security.addProvider(BouncyCastleProvider())
        return verifyAPKSignature(
Aayush Gupta's avatar
Aayush Gupta committed
114
115
116
117
118
119
120
121
122
123
124
125
            context,
            BufferedInputStream(
                FileInputStream(
                    applicationInfo.getApkFile(
                        context,
                        fullData.basicData
                    ).absolutePath
                )
            ),
            fullData.getLastVersion()!!.signature.byteInputStream(Charsets.UTF_8),
            context.assets.open("f-droid.org-signing-key.gpg")
        )
126
127
128
129
130
    }

    private fun isfDroidApplication(packageName: String): Boolean {
        var fDroidAppExistsResponse = 0
        FDroidAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
131
132
133
134
135
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            fDroidAppExistsResponse = searchResult[0]!!
136
137
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
138
139
140
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
141
                }
Aayush Gupta's avatar
Aayush Gupta committed
142
            }
143
144
145
146
147
148
        return fDroidAppExistsResponse == 200
    }

    private fun isSystemApplication(packageName: String): Boolean {
        var jsonResponse = ""
        SystemAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
149
150
151
152
153
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            jsonResponse = searchResult[0].toString()
154
155
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
156
157
158
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
159
                }
Aayush Gupta's avatar
Aayush Gupta committed
160
            }
161
        try {
162
163

            return JSONObject(jsonResponse).has(packageName);
164
165
166
        } catch (e: Exception) {
            if (e is JSONException) {
                Log.d(TAG, "$packageName is not a system application")
167
            } else {
168
                e.printStackTrace()
169
            }
170
        }
171
        return false
172
173
174
175
    }

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
Aayush Gupta's avatar
Aayush Gupta committed
176
177
        if (!verificationSuccessful) {
            Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
178
        }
179
180
    }

181
    private fun getApkFileSha1(file: File): String? {
182
183
184
185
186
187
188
189
190
191
        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)
            }
        }
192
193
        return byteArrayToHex(messageDigest.digest())
    }
194

195
196
197
198
    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()
199
200
201
    }

    private fun verifyAPKSignature(
Aayush Gupta's avatar
Aayush Gupta committed
202
203
204
205
206
        context: Context,
        apkInputStream: BufferedInputStream,
        apkSignatureInputStream: InputStream,
        publicKeyInputStream: InputStream
    ): Boolean {
narinder Rana's avatar
narinder Rana committed
207
        try {
208

narinder Rana's avatar
narinder Rana committed
209
            var jcaPGPObjectFactory =
Aayush Gupta's avatar
Aayush Gupta committed
210
                JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
narinder Rana's avatar
narinder Rana committed
211
            val pgpSignatureList: PGPSignatureList
212

narinder Rana's avatar
narinder Rana committed
213
214
215
216
217
218
219
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
220

narinder Rana's avatar
narinder Rana committed
221
            val pgpPublicKeyRingCollection =
Aayush Gupta's avatar
Aayush Gupta committed
222
223
224
225
                PGPPublicKeyRingCollection(
                    PGPUtil.getDecoderStream(publicKeyInputStream),
                    JcaKeyFingerprintCalculator()
                )
226

narinder Rana's avatar
narinder Rana committed
227
228
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
229

narinder Rana's avatar
narinder Rana committed
230
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
231

narinder Rana's avatar
narinder Rana committed
232
233
234
235
236
237
            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
238

narinder Rana's avatar
narinder Rana committed
239
240
241
242
243
244
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
245
246

            Handler(Looper.getMainLooper()).post {
247
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
narinder Rana's avatar
narinder Rana committed
248
            }
narinder Rana's avatar
narinder Rana committed
249
        }
narinder Rana's avatar
narinder Rana committed
250

Aayush Gupta's avatar
Aayush Gupta committed
251
        return false
252
    }
253
}
254
255
256
257

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