IntegrityVerificationTask.kt 10.3 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
import android.os.Environment
narinder Rana's avatar
narinder Rana committed
27
28
import android.os.Handler
import android.os.Looper
29
import android.util.Log
narinder Rana's avatar
narinder Rana committed
30
31
import android.widget.Toast
import foundation.e.apps.R
32
33
import foundation.e.apps.api.FDroidAppExistsRequest
import foundation.e.apps.api.SystemAppExistsRequest
34
35
import foundation.e.apps.application.model.data.FullData
import org.bouncycastle.jce.provider.BouncyCastleProvider
Arnau Vàzquez's avatar
Arnau Vàzquez committed
36
import org.bouncycastle.openpgp.PGPCompressedData
37
38
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
import org.bouncycastle.openpgp.PGPSignatureList
Arnau Vàzquez's avatar
Arnau Vàzquez committed
39
import org.bouncycastle.openpgp.PGPUtil
40
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
Arnau Vàzquez's avatar
Arnau Vàzquez committed
41
42
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
43
44
import org.json.JSONException
import org.json.JSONObject
Arnau Vàzquez's avatar
Arnau Vàzquez committed
45
46
47
48
49
50
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.Security
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)) {
narinder Rana's avatar
undo    
narinder Rana committed
68
69
                verifySystemSignature(context[0])
               // verifySystemValues(context[0])
narinder Rana's avatar
narinder Rana committed
70

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

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

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

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

    //get signature from apk and check



narinder Rana's avatar
narinder Rana committed
106
107
108
109
110
111
112
113
114
115
116
    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) {
            Log.e("TAG", ".................."+ info.packageName)
            Log.e("TAG", ".................."+ info.signatures)
narinder Rana's avatar
undo    
narinder Rana committed
117
        }
narinder Rana's avatar
narinder Rana committed
118
119
120
121
122
123

        return false;
//
//        return (fullData.basicData.id==systemJsonData.getString("project_id")
//                && fullData.basicData.name==systemJsonData.getString("app_name"))
    }
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

    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
141
    private fun verifyFdroidSignature(context: Context): Boolean {
142
143
        Security.addProvider(BouncyCastleProvider())
        return verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
144
145
146
147
148
149
150
151
152
153
154
                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
155
        )
156
157
158
159
160
    }

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

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

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
Aayush Gupta's avatar
Aayush Gupta committed
212
213
        if (!verificationSuccessful) {
            Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
214
        }
215
216
    }

217
    private fun getApkFileSha1(file: File): String? {
218
219
220
221
222
223
224
225
226
227
        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)
            }
        }
228
229
        return byteArrayToHex(messageDigest.digest())
    }
230

231
232
233
234
    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()
235
236
237
    }

    private fun verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
238
239
240
241
            context: Context,
            apkInputStream: BufferedInputStream,
            apkSignatureInputStream: InputStream,
            publicKeyInputStream: InputStream
Aayush Gupta's avatar
Aayush Gupta committed
242
    ): Boolean {
narinder Rana's avatar
narinder Rana committed
243
        try {
244

narinder Rana's avatar
narinder Rana committed
245
            var jcaPGPObjectFactory =
Aayush Gupta's avatar
Aayush Gupta committed
246
                JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
narinder Rana's avatar
narinder Rana committed
247
            val pgpSignatureList: PGPSignatureList
248

narinder Rana's avatar
narinder Rana committed
249
250
251
252
253
254
255
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
256

narinder Rana's avatar
narinder Rana committed
257
            val pgpPublicKeyRingCollection =
Aayush Gupta's avatar
Aayush Gupta committed
258
                PGPPublicKeyRingCollection(
narinder Rana's avatar
narinder Rana committed
259
260
                        PGPUtil.getDecoderStream(publicKeyInputStream),
                        JcaKeyFingerprintCalculator()
Aayush Gupta's avatar
Aayush Gupta committed
261
                )
262

narinder Rana's avatar
narinder Rana committed
263
264
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
265

narinder Rana's avatar
narinder Rana committed
266
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
267

narinder Rana's avatar
narinder Rana committed
268
269
270
271
272
273
            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
274

narinder Rana's avatar
narinder Rana committed
275
276
277
278
279
280
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
281
282

            Handler(Looper.getMainLooper()).post {
283
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
narinder Rana's avatar
narinder Rana committed
284
            }
narinder Rana's avatar
narinder Rana committed
285
        }
narinder Rana's avatar
narinder Rana committed
286

Aayush Gupta's avatar
Aayush Gupta committed
287
        return false
288
    }
289
}
290
291
292
293

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