IntegrityVerificationTask.kt 9.18 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
53
54
55
56
57
class IntegrityVerificationTask(
        private val applicationInfo: ApplicationInfo,
        private val fullData: FullData,
        private val integrityVerificationCallback: IntegrityVerificationCallback) :
        AsyncTask<Context, Void, Context>() {
    private var verificationSuccessful: Boolean = false
58
59
    private var TAG = "IntegrityVerificationTask"

Arnau Vàzquez's avatar
Arnau Vàzquez committed
60

61
    override fun doInBackground(vararg context: Context): Context {
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
       try {
           verificationSuccessful = if (isSystemApplication(fullData.packageName)) {
               verifySystemSignature(context[0])
           } else if (isfDroidApplication(fullData.packageName)) {
               verifyFdroidSignature(context[0])
           } else{
               checkGoogleApp(context[0])
           }
       }catch (e: Exception){
           e.printStackTrace()
       }
        return context[0]
    }

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

    private fun verifyShaSum(context: Context) :Boolean {
        if (!fullData.getLastVersion()!!.apkSHA.isNullOrEmpty()) {
            return getApkFileSha1(applicationInfo.getApkOrXapkFile(context, fullData, fullData.basicData)) ==
                    fullData.getLastVersion()!!.apkSHA
        }
        return false
    }

    private fun verifySystemSignature(context: Context): Boolean {
        if (!fullData.getLastVersion()?.signature.isNullOrEmpty()) {
            return fullData.getLastVersion()?.signature ==
                    getSystemSignature(context.packageManager)?.toCharsString()
        }
        return false
    }

    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
    }

    private fun verifyFdroidSignature(context: Context) : Boolean {
        Security.addProvider(BouncyCastleProvider())
        return verifyAPKSignature(
                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"))
    }

    private fun isfDroidApplication(packageName: String): Boolean {
        var fDroidAppExistsResponse = 0
        FDroidAppExistsRequest(packageName)
                .request { applicationError, searchResult ->
                    when (applicationError) {
                        null -> {
                            if (searchResult.size > 0) {
                                fDroidAppExistsResponse = searchResult[0]!!
                            }
                        }
                        else -> {
                            Log.e(TAG, applicationError.toString())
                        }
                    }
                }
        return fDroidAppExistsResponse == 200
    }

    private fun isSystemApplication(packageName: String): Boolean {
        var jsonResponse = ""
        SystemAppExistsRequest(packageName)
                .request { applicationError, searchResult ->
                    when (applicationError) {
                        null -> {
                            if (searchResult.size > 0) {
                                jsonResponse = searchResult[0].toString()
                            }
                        }
                        else -> {
                            Log.e(TAG, applicationError.toString())
                        }
                    }
                }
        try {
            if (packageName == JSONObject(jsonResponse).get(packageName)) {
                return true
            }
        } catch (e: Exception) {
            if (e is JSONException) {
                Log.d(TAG, "$packageName is not a system application")
163
            } else {
164
                e.printStackTrace()
165
            }
166
        }
167
        return false
168
169
170
171
    }

    override fun onPostExecute(context: Context) {
        integrityVerificationCallback.onIntegrityVerified(context, verificationSuccessful)
172
173
174
        if (!verificationSuccessful){
             Toast.makeText(context, R.string.not_able_install, Toast.LENGTH_LONG).show()
        }
175
176
    }

177
    private fun getApkFileSha1(file: File): String? {
178
179
180
181
182
183
184
185
186
187
        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)
            }
        }
188
189
        return byteArrayToHex(messageDigest.digest())
    }
190

191
192
193
194
    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()
195
196
197
    }

    private fun verifyAPKSignature(
narinder Rana's avatar
narinder Rana committed
198
            context: Context,
199
200
201
            apkInputStream: BufferedInputStream,
            apkSignatureInputStream: InputStream,
            publicKeyInputStream: InputStream): Boolean {
narinder Rana's avatar
narinder Rana committed
202
        try {
203

narinder Rana's avatar
narinder Rana committed
204
205
206
            var jcaPGPObjectFactory =
                    JcaPGPObjectFactory(PGPUtil.getDecoderStream(apkSignatureInputStream))
            val pgpSignatureList: PGPSignatureList
207

narinder Rana's avatar
narinder Rana committed
208
209
210
211
212
213
214
            val pgpObject = jcaPGPObjectFactory.nextObject()
            if (pgpObject is PGPCompressedData) {
                jcaPGPObjectFactory = JcaPGPObjectFactory(pgpObject.dataStream)
                pgpSignatureList = jcaPGPObjectFactory.nextObject() as PGPSignatureList
            } else {
                pgpSignatureList = pgpObject as PGPSignatureList
            }
215

narinder Rana's avatar
narinder Rana committed
216
217
218
219
            val pgpPublicKeyRingCollection =
                    PGPPublicKeyRingCollection(
                            PGPUtil.getDecoderStream(publicKeyInputStream),
                            JcaKeyFingerprintCalculator())
220

narinder Rana's avatar
narinder Rana committed
221
222
            val signature = pgpSignatureList.get(0)
            val key = pgpPublicKeyRingCollection.getPublicKey(signature.keyID)
Arnau Vàzquez's avatar
Arnau Vàzquez committed
223

narinder Rana's avatar
narinder Rana committed
224
            signature.init(BcPGPContentVerifierBuilderProvider(), key)
225

narinder Rana's avatar
narinder Rana committed
226
227
228
229
230
231
            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
232

narinder Rana's avatar
narinder Rana committed
233
234
235
236
237
238
            apkInputStream.close()
            apkSignatureInputStream.close()
            publicKeyInputStream.close()
            return signature.verify()
        } catch (e: Exception) {
            e.printStackTrace()
narinder Rana's avatar
narinder Rana committed
239
240

            Handler(Looper.getMainLooper()).post {
241
                Toast.makeText(context, context.resources.getString(R.string.Signature_verification_failed), Toast.LENGTH_LONG).show()
narinder Rana's avatar
narinder Rana committed
242
243
            }

narinder Rana's avatar
narinder Rana committed
244
        }
narinder Rana's avatar
narinder Rana committed
245

narinder Rana's avatar
narinder Rana committed
246
        return false;
247
248

    }
249
}
250
251
252
253

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