IntegrityVerificationTask.kt 10.4 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
        try {
narinder Rana's avatar
narinder Rana committed
64
            var packageName = getAPK_PackageName(context[0])
narinder Rana's avatar
narinder Rana committed
65
            verificationSuccessful = if (isSystemApplication(packageName.toString())) {
66
                verifyAPKSignature(context[0])
narinder Rana's avatar
narinder Rana committed
67
            } else if (isfDroidApplication(packageName.toString())) {
Aayush Gupta's avatar
Aayush Gupta committed
68
69
70
71
72
73
74
                verifyFdroidSignature(context[0])
            } else {
                checkGoogleApp(context[0])
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
75
76
77
78
79
80
81
        return context[0]
    }

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

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

narinder Rana's avatar
undo    
narinder Rana committed
90

narinder Rana's avatar
narinder Rana committed
91
    // get signature from apk and check
92
93
    private fun verifyAPKSignature(context: Context): Boolean {

narinder Rana's avatar
narinder Rana committed
94
95
        // get Signature from APK
        if (getAPKSignature(context) != null) {
96
            return getAPKSignature(context)?.toCharsString() ==
narinder Rana's avatar
narinder Rana committed
97
                getSystemSignature(context.packageManager)?.toCharsString()
98
99
100
        }
        return false
    }
narinder Rana's avatar
undo    
narinder Rana committed
101

102
103
104
    private fun getAPKSignature(context: Context): Signature? {
        try {
            val fullPath: String = applicationInfo.getApkFile(
narinder Rana's avatar
narinder Rana committed
105
106
                context,
                fullData.basicData
107
108
109
110
111
112
113
114
115
            ).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
116

narinder Rana's avatar
narinder Rana committed
117
    private fun getAPK_PackageName(context: Context): String? {
narinder Rana's avatar
narinder Rana committed
118
119
120

        val pm: PackageManager = context.packageManager
        val fullPath: String = applicationInfo.getApkFile(
narinder Rana's avatar
narinder Rana committed
121
122
            context,
            fullData.basicData
narinder Rana's avatar
narinder Rana committed
123
124
125
        ).absolutePath
        val info = pm.getPackageArchiveInfo(fullPath, 0)
        if (info != null) {
narinder Rana's avatar
narinder Rana committed
126
127
128
            return info.packageName
        } else
            return null
narinder Rana's avatar
narinder Rana committed
129
    }
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

    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
147
    private fun verifyFdroidSignature(context: Context): Boolean {
148
149
        Security.addProvider(BouncyCastleProvider())
        return verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
150
151
152
153
154
155
156
157
158
159
160
            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
161
        )
162
163
164
165
166
    }

    private fun isfDroidApplication(packageName: String): Boolean {
        var fDroidAppExistsResponse = 0
        FDroidAppExistsRequest(packageName)
Aayush Gupta's avatar
Aayush Gupta committed
167
168
169
170
171
            .request { applicationError, searchResult ->
                when (applicationError) {
                    null -> {
                        if (searchResult.size > 0) {
                            fDroidAppExistsResponse = searchResult[0]!!
172
173
                        }
                    }
Aayush Gupta's avatar
Aayush Gupta committed
174
175
176
                    else -> {
                        Log.e(TAG, applicationError.toString())
                    }
177
                }
Aayush Gupta's avatar
Aayush Gupta committed
178
            }
179
180
181
182
183
184
        return fDroidAppExistsResponse == 200
    }

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

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
Aayush Gupta's avatar
Aayush Gupta committed
217
218
        if (!verificationSuccessful) {
            Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
219
        }
220
221
    }

222
    private fun getApkFileSha1(file: File): String? {
223
224
225
226
227
228
229
230
231
232
        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)
            }
        }
233
234
        return byteArrayToHex(messageDigest.digest())
    }
235

236
237
238
239
    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()
240
241
242
    }

    private fun verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
243
244
245
246
        context: Context,
        apkInputStream: BufferedInputStream,
        apkSignatureInputStream: InputStream,
        publicKeyInputStream: InputStream
Aayush Gupta's avatar
Aayush Gupta committed
247
    ): Boolean {
narinder Rana's avatar
narinder Rana committed
248
        try {
249

narinder Rana's avatar
narinder Rana committed
250
            var jcaPGPObjectFactory =
Aayush Gupta's avatar
Aayush Gupta committed
251
                JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
narinder Rana's avatar
narinder Rana committed
252
            val pgpSignatureList: PGPSignatureList
253

narinder Rana's avatar
narinder Rana committed
254
255
256
257
258
259
260
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
261

narinder Rana's avatar
narinder Rana committed
262
            val pgpPublicKeyRingCollection =
Aayush Gupta's avatar
Aayush Gupta committed
263
                PGPPublicKeyRingCollection(
narinder Rana's avatar
narinder Rana committed
264
265
                    PGPUtil.getDecoderStream(publicKeyInputStream),
                    JcaKeyFingerprintCalculator()
Aayush Gupta's avatar
Aayush Gupta committed
266
                )
267

narinder Rana's avatar
narinder Rana committed
268
269
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
270

narinder Rana's avatar
narinder Rana committed
271
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
272

narinder Rana's avatar
narinder Rana committed
273
274
275
276
277
278
            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
279

narinder Rana's avatar
narinder Rana committed
280
281
282
283
284
285
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
286
287

            Handler(Looper.getMainLooper()).post {
288
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
narinder Rana's avatar
narinder Rana committed
289
            }
narinder Rana's avatar
narinder Rana committed
290
        }
narinder Rana's avatar
narinder Rana committed
291

Aayush Gupta's avatar
Aayush Gupta committed
292
        return false
293
    }
294
}
295
296
297
298

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