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
narinder Rana committed
68
69
70
                //verifySystemSignature(context[0])
                verifySystemValues(context[0])

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()
narinder Rana's avatar
narinder Rana committed
98
99
100
        }
        else{

101
102
103
        }
        return false
    }
narinder Rana's avatar
narinder Rana committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    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)
        };

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

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

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

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

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

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

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

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

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

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

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

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

narinder Rana's avatar
narinder Rana committed
264
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
265

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

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

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

Aayush Gupta's avatar
Aayush Gupta committed
285
        return false
286
    }
287
}
288
289
290
291

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