IntegrityVerificationTask.kt 11.2 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

52
class IntegrityVerificationTask(
narinder Rana's avatar
narinder Rana committed
53
54
55
        private val applicationInfo: ApplicationInfo,
        private val fullData: FullData,
        private val integrityVerificationCallback: IntegrityVerificationCallback
Aayush Gupta's avatar
Aayush Gupta committed
56
) :
narinder Rana's avatar
narinder Rana committed
57

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

narinder Rana's avatar
narinder Rana committed
63
64


65
    override fun doInBackground(vararg context: Context): Context {
Aayush Gupta's avatar
Aayush Gupta committed
66
67
        try {
            verificationSuccessful = if (isSystemApplication(fullData.packageName)) {
68
69
70

                verifyAPKSignature(context[0])
            // verifySystemSignature(context[0])
narinder Rana's avatar
undo    
narinder Rana committed
71
               // verifySystemValues(context[0])
narinder Rana's avatar
narinder Rana committed
72

Aayush Gupta's avatar
Aayush Gupta committed
73
74
75
76
77
78
79
80
            } else if (isfDroidApplication(fullData.packageName)) {
                verifyFdroidSignature(context[0])
            } else {
                checkGoogleApp(context[0])
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
81
82
83
84
85
86
87
        return context[0]
    }

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

Aayush Gupta's avatar
Aayush Gupta committed
88
    private fun verifyShaSum(context: Context): Boolean {
89
90
        if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
            return getApkFileSha1(applicationInfo.getApkOrXapkFile(context, fullData, fullData.basicData)) ==
Aayush Gupta's avatar
Aayush Gupta committed
91
                fullData.getLastVersion()!!.apkSHA
92
93
94
95
96
97
98
        }
        return false
    }

    private fun verifySystemSignature(context: Context): Boolean {
        if (!fullData.getLastVersion()?.signature.isNullOrEmpty()) {
            return fullData.getLastVersion()?.signature ==
Aayush Gupta's avatar
Aayush Gupta committed
99
                getSystemSignature(context.packageManager)?.toCharsString()
100
101
102
        }
        return false
    }
narinder Rana's avatar
undo    
narinder Rana committed
103
104

    //get signature from apk and check
105
106
107
108
109
110
111
112
113
    private fun verifyAPKSignature(context: Context): Boolean {

        //get Signature from APK
        if (getAPKSignature(context)!=null) {
            return getAPKSignature(context)?.toCharsString() ==
                    getSystemSignature(context.packageManager)?.toCharsString()
        }
        return false
    }
narinder Rana's avatar
undo    
narinder Rana committed
114

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    private fun getAPKSignature(context: Context): Signature? {
        try {
            val fullPath: String = applicationInfo.getApkFile(
                    context,
                    fullData.basicData
            ).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
130
131


narinder Rana's avatar
narinder Rana committed
132
133
134
135
136
137
138
139
140
    private fun verifySystemValues(context: Context): Boolean {

        val pm: PackageManager = context.packageManager
        val fullPath: String = applicationInfo.getApkFile(
                context,
                fullData.basicData
        ).absolutePath
        val info = pm.getPackageArchiveInfo(fullPath, 0)
        if (info != null) {
141
142
            Log.e("TAG", ".................." + info.packageName)
            Log.e("TAG", ".................." + info.signatures)
narinder Rana's avatar
undo    
narinder Rana committed
143
        }
narinder Rana's avatar
narinder Rana committed
144
145
146
147
148
149

        return false;
//
//        return (fullData.basicData.id==systemJsonData.getString("project_id")
//                && fullData.basicData.name==systemJsonData.getString("app_name"))
    }
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166

    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
167
    private fun verifyFdroidSignature(context: Context): Boolean {
168
169
        Security.addProvider(BouncyCastleProvider())
        return verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
170
171
172
173
174
175
176
177
178
179
180
                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
181
        )
182
183
184
185
186
    }

    private fun isfDroidApplication(packageName: String): Boolean {
        var fDroidAppExistsResponse = 0
        FDroidAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
187
188
189
190
191
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            fDroidAppExistsResponse = searchResult[0]!!
192
193
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
194
195
196
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
197
                }
Aayush Gupta's avatar
Aayush Gupta committed
198
            }
199
200
201
202
203
204
        return fDroidAppExistsResponse == 200
    }

    private fun isSystemApplication(packageName: String): Boolean {
        var jsonResponse = ""
        SystemAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
205
206
207
208
209
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            jsonResponse = searchResult[0].toString()
210
211
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
212
213
214
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
215
                }
Aayush Gupta's avatar
Aayush Gupta committed
216
            }
217
        try {
narinder Rana's avatar
narinder Rana committed
218
219
220
221
222
223
224
225
            if(JSONObject(jsonResponse).has(packageName)){
                 systemJsonData = JSONObject(jsonResponse).getJSONObject(packageName);
                return true
            }
            else{
                return false
            }
           // return JSONObject(jsonResponse).has(packageName);
226
227
228
        } catch (e: Exception) {
            if (e is JSONException) {
                Log.d(TAG, "$packageName is not a system application")
229
            } else {
230
                e.printStackTrace()
231
            }
232
        }
233
        return false
234
235
236
237
    }

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
Aayush Gupta's avatar
Aayush Gupta committed
238
239
        if (!verificationSuccessful) {
            Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
240
        }
241
242
    }

243
    private fun getApkFileSha1(file: File): String? {
244
245
246
247
248
249
250
251
252
253
        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)
            }
        }
254
255
        return byteArrayToHex(messageDigest.digest())
    }
256

257
258
259
260
    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()
261
262
263
    }

    private fun verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
264
265
266
267
            context: Context,
            apkInputStream: BufferedInputStream,
            apkSignatureInputStream: InputStream,
            publicKeyInputStream: InputStream
Aayush Gupta's avatar
Aayush Gupta committed
268
    ): Boolean {
narinder Rana's avatar
narinder Rana committed
269
        try {
270

narinder Rana's avatar
narinder Rana committed
271
            var jcaPGPObjectFactory =
Aayush Gupta's avatar
Aayush Gupta committed
272
                JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
narinder Rana's avatar
narinder Rana committed
273
            val pgpSignatureList: PGPSignatureList
274

narinder Rana's avatar
narinder Rana committed
275
276
277
278
279
280
281
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
282

narinder Rana's avatar
narinder Rana committed
283
            val pgpPublicKeyRingCollection =
Aayush Gupta's avatar
Aayush Gupta committed
284
                PGPPublicKeyRingCollection(
narinder Rana's avatar
narinder Rana committed
285
286
                        PGPUtil.getDecoderStream(publicKeyInputStream),
                        JcaKeyFingerprintCalculator()
Aayush Gupta's avatar
Aayush Gupta committed
287
                )
288

narinder Rana's avatar
narinder Rana committed
289
290
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
291

narinder Rana's avatar
narinder Rana committed
292
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
293

narinder Rana's avatar
narinder Rana committed
294
295
296
297
298
299
            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
300

narinder Rana's avatar
narinder Rana committed
301
302
303
304
305
306
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
307
308

            Handler(Looper.getMainLooper()).post {
309
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
narinder Rana's avatar
narinder Rana committed
310
            }
narinder Rana's avatar
narinder Rana committed
311
        }
narinder Rana's avatar
narinder Rana committed
312

Aayush Gupta's avatar
Aayush Gupta committed
313
        return false
314
    }
315
}
316
317
318
319

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