[Android] Add support for calling package validation and logging

Robert Stone git at videolan.org
Tue May 11 06:27:09 UTC 2021


vlc-android | branch: master | Robert Stone <rhstone at gmail.com> | Mon May  3 23:28:13 2021 -0700| [ad225ea5dd3580d5538b23b592623856119df47a] | committer: Nicolas Pomepuy

Add support for calling package validation and logging

> https://code.videolan.org/videolan/vlc-android/commit/ad225ea5dd3580d5538b23b592623856119df47a
---

 .../vlc-android/assets/authorized_keys.json        |  72 ++++++++++
 .../src/org/videolan/vlc/util/AccessControl.kt     | 157 +++++++++++++++++++++
 2 files changed, 229 insertions(+)

diff --git a/application/vlc-android/assets/authorized_keys.json b/application/vlc-android/assets/authorized_keys.json
new file mode 100644
index 000000000..698e22483
--- /dev/null
+++ b/application/vlc-android/assets/authorized_keys.json
@@ -0,0 +1,72 @@
+[
+  {
+    "name"   : "Android Auto",
+    "package": "com.google.android.projection.gearhead",
+    "keys"   : [
+      {
+        "release": "true",
+        "keyId"  : "fd:b0:0c:43:db:de:8b:51:cb:31:2a:a8:1d:3b:5f:a1:77:13:ad:b9:4b:28:f5:98:d7:7f:8e:b8:9d:ac:ee:df"
+      },
+      {
+        "release": "false",
+        "keyId"  : "19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00"
+      },
+      {
+        "release": "false",
+        "keyId"  : "70:81:1a:3e:ac:fd:2e:83:e1:8d:a9:bf:ed:e5:2d:f1:6c:e9:1f:2e:69:a4:4d:21:f1:8a:b6:69:91:13:07:71"
+      }
+    ]
+  },
+  {
+    "name"   : "WearOS",
+    "package": "com.google.android.wearable.app",
+    "keys"   : [
+      {
+        "release": "true",
+        "keyId"  : "85:cd:59:73:54:1b:e6:f4:77:d8:47:a0:bc:c6:aa:25:27:68:4b:81:9c:d5:96:85:29:66:4c:b0:71:57:b6:fe"
+      },
+      {
+        "release": "false",
+        "keyId"  : "69:d0:72:16:9a:2c:6b:2f:5a:cc:59:0c:e4:33:a1:1a:c3:df:55:1a:df:ee:5d:5f:63:c0:83:b7:22:76:2e:19"
+      }
+    ]
+  },
+  {
+    "name"   : "Android Auto Simulator",
+    "package": "com.google.android.autosimulator",
+    "keys"   : [
+      {
+        "release": "true",
+        "keyId"  : "19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00"
+      }
+    ]
+  },
+  {
+    "name"   : "Google",
+    "package": "com.google.android.googlequicksearchbox",
+    "keys"   : [
+      {
+        "release": "true",
+        "keyId"  : "f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83"
+      },
+      {
+        "release": "false",
+        "keyId"  : "19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00"
+      }
+    ]
+  },
+  {
+    "name"   : "Google Assistant on Android Automotive OS",
+    "package": "com.google.android.carassistant",
+    "keys"   : [
+      {
+        "release": "true",
+        "keyId"  : "74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2"
+      },
+      {
+        "release": "false",
+        "keyId"  : "17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15"
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/application/vlc-android/src/org/videolan/vlc/util/AccessControl.kt b/application/vlc-android/src/org/videolan/vlc/util/AccessControl.kt
new file mode 100644
index 000000000..a56c8ba38
--- /dev/null
+++ b/application/vlc-android/src/org/videolan/vlc/util/AccessControl.kt
@@ -0,0 +1,157 @@
+/*****************************************************************************
+ * AccessControl.kt
+ *
+ * Copyright © 2021 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ */
+
+package org.videolan.vlc.util
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Process
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.json.JSONArray
+import org.json.JSONObject
+import org.videolan.resources.AppContextProvider
+import org.videolan.resources.BuildConfig
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+
+private const val TAG = "VLC/AccessControl"
+
+data class CertInfo(val name: String, val keys: List<String>)
+data class AuthEntry(val approved: Boolean, val callingPackage: String?, val desc: String)
+
+object AccessControl {
+    private val platformSignature: String?
+    private val callingUidChecked = mutableMapOf<Int, AuthEntry>()
+    private val certificateAllowList by lazy { runBlocking { loadAuthorizedKeys() } }
+
+    init {
+        platformSignature = getSignature(AppContextProvider.appContext, "android")
+    }
+
+    /**
+     * Perform security checks to determine if the callingUid is authorized.
+     * @param callingUid The calling application's user id.
+     * @param clientPackageName The calling application's package name. If not specified, an attempt
+     * will be made to resolve the package name from the package manager.
+     */
+    fun logCaller(callingUid: Int, clientPackageName: String? = null) {
+        callingUidChecked[callingUid]?.let {
+            return
+        }
+        val ctx = AppContextProvider.appContext
+        val callingPackage = getCallingPackage(ctx, callingUid, clientPackageName)
+        when {
+            callingUid == Process.myUid() -> {
+                Log.i(TAG, "Known access from self (${BuildConfig.APP_ID}) to VLC")
+                callingUidChecked[callingUid] = AuthEntry(true, BuildConfig.APP_ID, "VLC UID")
+                return
+            }
+            callingUid == Process.SYSTEM_UID -> {
+                Log.i(TAG, "Known access from system to VLC")
+                callingUidChecked[callingUid] = AuthEntry(true, "system", "System Process")
+                return
+            }
+            callingPackage != null -> {
+                val callingSignature = getSignature(ctx, callingPackage)
+                if (callingSignature == platformSignature) {
+                    Log.i(TAG, "Known access from Android platform ($callingPackage) to VLC")
+                    callingUidChecked[callingUid] = AuthEntry(true, callingPackage, "Known Platform Signature")
+                    return
+                }
+                val certs = certificateAllowList[callingPackage]
+                certs?.keys?.forEach { key ->
+                    if (callingSignature == key) {
+                        Log.i(TAG, "Known access from ${certs.name} ($callingPackage) to VLC")
+                        callingUidChecked[callingUid] = AuthEntry(true, callingPackage, "Known App Signature")
+                        return
+                    }
+                }
+                Log.i(TAG, "Unknown access from signature $callingSignature ($callingPackage) to VLC")
+                callingUidChecked[callingUid] = AuthEntry(false, callingPackage, "Unknown Signature")
+            }
+        }
+        Log.i(TAG, "Access history: $callingUidChecked")
+    }
+
+    fun getCallingPackage(ctx: Context, callingUid: Int, clientPackageName: String? = null): String? {
+        val packages = ctx.packageManager.getPackagesForUid(callingUid) ?: return null
+        val packageName = packages.firstOrNull()
+        return if (clientPackageName == null || clientPackageName == packageName) {
+            packageName
+        } else {
+            Log.i(TAG, "Unexpected package name mismatch between $clientPackageName and $packageName")
+            null
+        }
+    }
+
+    /**
+     * Read authorized keys into memory. Keys are stored in a JSON data file.
+     */
+    private suspend fun loadAuthorizedKeys(): Map<String, CertInfo> {
+        return withContext(Dispatchers.IO) {
+            val certificateAllowList = mutableMapOf<String, CertInfo>()
+            val ctx = AppContextProvider.appContext
+            val jsonData = ctx.assets.open("authorized_keys.json").bufferedReader().use {
+                it.readText()
+            }
+            val signatures = JSONArray(jsonData)
+            for (i in 0 until signatures.length()) {
+                val s = signatures[i] as JSONObject
+                val keys = s.getJSONArray("keys")
+                val keyList = arrayListOf<String>()
+                for (j in 0 until keys.length()) {
+                    val keyId = (keys[j] as JSONObject).getString("keyId")
+                    keyList.add(keyId)
+                }
+                val name = s.getString("name")
+                val packageName = s.getString("package")
+                certificateAllowList[packageName] = CertInfo(name, keyList)
+            }
+            certificateAllowList
+        }
+    }
+
+    @Suppress("deprecation")
+    private fun getSignature(ctx: Context, callingPackage: String): String? {
+        try {
+            val packageInfo = ctx.packageManager.getPackageInfo(callingPackage, PackageManager.GET_SIGNATURES)
+            if (packageInfo.signatures != null && packageInfo.signatures.size == 1) {
+                return genSigSha256(packageInfo.signatures[0].toByteArray())
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(TAG, "Calling package name not found: $callingPackage", e)
+        }
+        return null
+    }
+
+    private fun genSigSha256(certificate: ByteArray): String? {
+        try {
+            val md = MessageDigest.getInstance("SHA-256")
+            md.update(certificate)
+            return md.digest().joinToString(":") { String.format("%02x", it) }
+        } catch (e: NoSuchAlgorithmException) {
+            Log.e(TAG, "Message digest algorithm SHA-256 not found", e)
+        }
+        return null
+    }
+}



More information about the Android mailing list