IntegrityVerificationTask.kt 10.7 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(
narinder Rana's avatar
narinder Rana committed
52
53
54
    private val applicationInfo: ApplicationInfo,
    private val fullData: FullData,
    private val integrityVerificationCallback: IntegrityVerificationCallback
Aayush Gupta's avatar
Aayush Gupta committed
55
) :
narinder Rana's avatar
narinder Rana committed
56

Aayush Gupta's avatar
Aayush Gupta committed
57
    AsyncTask<Context, Void, Context>() {
narinder Rana's avatar
narinder Rana committed
58
    private lateinit var systemJsonData: JSONObject
59
    private var verificationSuccessful: Boolean = false
60
61
    private var TAG = "IntegrityVerificationTask"

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

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

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

    private fun verifySystemSignature(context: Context): Boolean {
        if (!fullData.getLastVersion()?.signature.isNullOrEmpty()) {
            return fullData.getLastVersion()?.signature ==
Aayush Gupta's avatar
Aayush Gupta committed
92
                getSystemSignature(context.packageManager)?.toCharsString()
93
94
95
        }
        return false
    }
narinder Rana's avatar
undo    
narinder Rana committed
96

narinder Rana's avatar
narinder Rana committed
97
    // get signature from apk and check
98
99
    private fun verifyAPKSignature(context: Context): Boolean {

narinder Rana's avatar
narinder Rana committed
100
101
        // get Signature from APK
        if (getAPKSignature(context) != null) {
102
            return getAPKSignature(context)?.toCharsString() ==
narinder Rana's avatar
narinder Rana committed
103
                getSystemSignature(context.packageManager)?.toCharsString()
104
105
106
        }
        return false
    }
narinder Rana's avatar
undo    
narinder Rana committed
107

108
109
110
    private fun getAPKSignature(context: Context): Signature? {
        try {
            val fullPath: String = applicationInfo.getApkFile(
narinder Rana's avatar
narinder Rana committed
111
112
                context,
                fullData.basicData
113
114
115
116
117
118
119
120
121
            ).absolutePath

            val releaseSig = context.packageManager.getPackageArchiveInfo(fullPath, PackageManager.GET_SIGNATURES)
            return getFirstSignature(releaseSig)
        } catch (e: PackageManager.NameNotFoundException) {
            Log.d(TAG, "Unable to find the package: android")
        }
        return null
    }
narinder Rana's avatar
undo    
narinder Rana committed
122

narinder Rana's avatar
narinder Rana committed
123
124
125
126
    private fun verifySystemValues(context: Context): Boolean {

        val pm: PackageManager = context.packageManager
        val fullPath: String = applicationInfo.getApkFile(
narinder Rana's avatar
narinder Rana committed
127
128
            context,
            fullData.basicData
narinder Rana's avatar
narinder Rana committed
129
130
131
        ).absolutePath
        val info = pm.getPackageArchiveInfo(fullPath, 0)
        if (info != null) {
132
133
            Log.e("TAG", ".................." + info.packageName)
            Log.e("TAG", ".................." + info.signatures)
narinder Rana's avatar
undo    
narinder Rana committed
134
        }
narinder Rana's avatar
narinder Rana committed
135
        return false
narinder Rana's avatar
narinder Rana committed
136
    }
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

    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
154
    private fun verifyFdroidSignature(context: Context): Boolean {
155
156
        Security.addProvider(BouncyCastleProvider())
        return verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
157
158
159
160
161
162
163
164
165
166
167
            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")
Aayush Gupta's avatar
Aayush Gupta committed
168
        )
169
170
171
172
173
    }

    private fun isfDroidApplication(packageName: String): Boolean {
        var fDroidAppExistsResponse = 0
        FDroidAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
174
175
176
177
178
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            fDroidAppExistsResponse = searchResult[0]!!
179
180
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
181
182
183
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
184
                }
Aayush Gupta's avatar
Aayush Gupta committed
185
            }
186
187
188
189
190
191
        return fDroidAppExistsResponse == 200
    }

    private fun isSystemApplication(packageName: String): Boolean {
        var jsonResponse = ""
        SystemAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
192
193
194
195
196
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            jsonResponse = searchResult[0].toString()
197
198
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
199
200
201
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
202
                }
Aayush Gupta's avatar
Aayush Gupta committed
203
            }
204
        try {
narinder Rana's avatar
narinder Rana committed
205
206
            if (JSONObject(jsonResponse).has(packageName)) {
                systemJsonData = JSONObject(jsonResponse).getJSONObject(packageName)
narinder Rana's avatar
narinder Rana committed
207
                return true
narinder Rana's avatar
narinder Rana committed
208
            } else {
narinder Rana's avatar
narinder Rana committed
209
210
                return false
            }
narinder Rana's avatar
narinder Rana committed
211
            // return JSONObject(jsonResponse).has(packageName);
212
213
214
        } catch (e: Exception) {
            if (e is JSONException) {
                Log.d(TAG, "$packageName is not a system application")
215
            } else {
216
                e.printStackTrace()
217
            }
218
        }
219
        return false
220
221
222
223
    }

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
Aayush Gupta's avatar
Aayush Gupta committed
224
225
        if (!verificationSuccessful) {
            Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
226
        }
227
228
    }

229
    private fun getApkFileSha1(file: File): String? {
230
231
232
233
234
235
236
237
238
239
        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)
            }
        }
240
241
        return byteArrayToHex(messageDigest.digest())
    }
242

243
244
245
246
    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()
247
248
249
    }

    private fun verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
250
251
252
253
        context: Context,
        apkInputStream: BufferedInputStream,
        apkSignatureInputStream: InputStream,
        publicKeyInputStream: InputStream
Aayush Gupta's avatar
Aayush Gupta committed
254
    ): Boolean {
narinder Rana's avatar
narinder Rana committed
255
        try {
256

narinder Rana's avatar
narinder Rana committed
257
            var jcaPGPObjectFactory =
Aayush Gupta's avatar
Aayush Gupta committed
258
                JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
narinder Rana's avatar
narinder Rana committed
259
            val pgpSignatureList: PGPSignatureList
260

narinder Rana's avatar
narinder Rana committed
261
262
263
264
265
266
267
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
268

narinder Rana's avatar
narinder Rana committed
269
            val pgpPublicKeyRingCollection =
Aayush Gupta's avatar
Aayush Gupta committed
270
                PGPPublicKeyRingCollection(
narinder Rana's avatar
narinder Rana committed
271
272
                    PGPUtil.getDecoderStream(publicKeyInputStream),
                    JcaKeyFingerprintCalculator()
Aayush Gupta's avatar
Aayush Gupta committed
273
                )
274

narinder Rana's avatar
narinder Rana committed
275
276
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
277

narinder Rana's avatar
narinder Rana committed
278
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
279

narinder Rana's avatar
narinder Rana committed
280
281
282
283
284
285
            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
286

narinder Rana's avatar
narinder Rana committed
287
288
289
290
291
292
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
293
294

            Handler(Looper.getMainLooper()).post {
295
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
narinder Rana's avatar
narinder Rana committed
296
            }
narinder Rana's avatar
narinder Rana committed
297
        }
narinder Rana's avatar
narinder Rana committed
298

Aayush Gupta's avatar
Aayush Gupta committed
299
        return false
300
    }
301
}
302
303
304
305

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