[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