Skip to content

Commit 95d5fc1

Browse files
Vending: Added PI access management (#3108)
Co-authored-by: Marvin W <git@larma.de>
1 parent 2aae6af commit 95d5fc1

17 files changed

Lines changed: 356 additions & 88 deletions

File tree

play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ object SettingsContract {
279279
const val ASSET_DEVICE_SYNC = "vending_device_sync"
280280
const val APPS_INSTALL = "vending_apps_install"
281281
const val APPS_INSTALLER_LIST = "vending_apps_installer_list"
282+
const val PLAY_INTEGRITY_APP_LIST = "vending_play_integrity_apps"
282283

283284
val PROJECTION = arrayOf(
284285
LICENSING,
@@ -289,6 +290,7 @@ object SettingsContract {
289290
ASSET_DEVICE_SYNC,
290291
APPS_INSTALL,
291292
APPS_INSTALLER_LIST,
293+
PLAY_INTEGRITY_APP_LIST
292294
)
293295
}
294296

play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ class SettingsProvider : ContentProvider() {
369369
Vending.SPLIT_INSTALL -> getSettingsBoolean(key, false)
370370
Vending.APPS_INSTALL -> getSettingsBoolean(key, false)
371371
Vending.APPS_INSTALLER_LIST -> getSettingsString(key, "")
372+
Vending.PLAY_INTEGRITY_APP_LIST -> getSettingsString(key, "")
372373
else -> throw IllegalArgumentException("Unknown key: $key")
373374
}
374375
}
@@ -386,6 +387,7 @@ class SettingsProvider : ContentProvider() {
386387
Vending.ASSET_DEVICE_SYNC -> editor.putBoolean(key, value as Boolean)
387388
Vending.APPS_INSTALL -> editor.putBoolean(key, value as Boolean)
388389
Vending.APPS_INSTALLER_LIST -> editor.putString(key, value as String)
390+
Vending.PLAY_INTEGRITY_APP_LIST -> editor.putString(key, value as String)
389391
else -> throw IllegalArgumentException("Unknown key: $key")
390392
}
391393
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2025 microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.microg.gms.vending
7+
8+
import org.json.JSONException
9+
import org.json.JSONObject
10+
11+
class PlayIntegrityData(var allowed: Boolean,
12+
val packageName: String,
13+
val pkgSignSha256: String,
14+
var lastTime: Long,
15+
var lastResult: String? = null,
16+
var lastStatus: Boolean = false) {
17+
18+
override fun toString(): String {
19+
return JSONObject()
20+
.put(ALLOWED, allowed)
21+
.put(PACKAGE_NAME, packageName)
22+
.put(SIGNATURE, pkgSignSha256)
23+
.put(LAST_VISIT_TIME, lastTime)
24+
.put(LAST_VISIT_RESULT, lastResult)
25+
.put(LAST_VISIT_STATUS, lastStatus)
26+
.toString()
27+
}
28+
29+
companion object {
30+
private const val PACKAGE_NAME = "packageName"
31+
private const val ALLOWED = "allowed"
32+
private const val SIGNATURE = "signature"
33+
private const val LAST_VISIT_TIME = "lastVisitTime"
34+
private const val LAST_VISIT_RESULT = "lastVisitResult"
35+
private const val LAST_VISIT_STATUS = "lastVisitStatus"
36+
37+
private fun parse(jsonString: String): PlayIntegrityData? {
38+
try {
39+
val json = JSONObject(jsonString)
40+
return PlayIntegrityData(
41+
json.getBoolean(ALLOWED),
42+
json.getString(PACKAGE_NAME),
43+
json.getString(SIGNATURE),
44+
json.getLong(LAST_VISIT_TIME),
45+
json.getString(LAST_VISIT_RESULT),
46+
json.getBoolean(LAST_VISIT_STATUS)
47+
)
48+
} catch (e: JSONException) {
49+
return null
50+
}
51+
}
52+
53+
fun loadDataSet(content: String): Set<PlayIntegrityData> {
54+
return content.split("|").mapNotNull { parse(it) }.toSet()
55+
}
56+
57+
fun updateDataSetString(channelList: Set<PlayIntegrityData>, channel: PlayIntegrityData): String {
58+
val channelData = channelList.find { it.packageName == channel.packageName && it.pkgSignSha256 == channel.pkgSignSha256 }
59+
val newChannelList = if (channelData != null) {
60+
channelData.allowed = channel.allowed
61+
channelData.lastTime = channel.lastTime
62+
channelData.lastResult = channel.lastResult
63+
channelData.lastStatus = channel.lastStatus
64+
channelList
65+
} else {
66+
channelList + channel
67+
}
68+
return newChannelList.let { it -> it.joinToString(separator = "|") { it.toString() } }
69+
}
70+
}
71+
}

play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAllAppsFragment.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import com.google.android.gms.R
1818
import kotlinx.coroutines.Dispatchers
1919
import kotlinx.coroutines.withContext
2020
import org.microg.gms.safetynet.SafetyNetDatabase
21+
import org.microg.gms.vending.PlayIntegrityData
22+
import org.microg.gms.vending.VendingPreferences
2123

2224
class SafetyNetAllAppsFragment : PreferenceFragmentCompat() {
2325
private lateinit var database: SafetyNetDatabase
@@ -50,8 +52,10 @@ class SafetyNetAllAppsFragment : PreferenceFragmentCompat() {
5052
private fun updateContent() {
5153
val context = requireContext()
5254
lifecycleScope.launchWhenResumed {
55+
val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context)
5356
val apps = withContext(Dispatchers.IO) {
54-
val res = database.recentApps.map { app ->
57+
val playPairs = PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime }
58+
val res = (database.recentApps + playPairs).map { app ->
5559
val pref = AppIconPreference(context)
5660
pref.packageName = app.first
5761
pref.summary = when {

play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAppFragment.kt

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,26 @@ package org.microg.gms.ui
88
import android.annotation.SuppressLint
99
import android.os.Bundle
1010
import android.text.format.DateUtils
11+
import androidx.core.content.ContextCompat
1112
import androidx.lifecycle.lifecycleScope
12-
import androidx.preference.*
13+
import androidx.preference.Preference
14+
import androidx.preference.PreferenceCategory
15+
import androidx.preference.PreferenceFragmentCompat
16+
import androidx.preference.SwitchPreferenceCompat
17+
import androidx.preference.isEmpty
1318
import com.google.android.gms.R
1419
import org.microg.gms.safetynet.SafetyNetDatabase
15-
import org.microg.gms.safetynet.SafetyNetRequestType.*
20+
import org.microg.gms.safetynet.SafetyNetRequestType.ATTESTATION
21+
import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA
22+
import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA_ENTERPRISE
23+
import org.microg.gms.vending.PlayIntegrityData
24+
import org.microg.gms.vending.VendingPreferences
1625

1726
class SafetyNetAppFragment : PreferenceFragmentCompat() {
1827
private lateinit var appHeadingPreference: AppHeadingPreference
1928
private lateinit var recents: PreferenceCategory
2029
private lateinit var recentsNone: Preference
30+
private lateinit var allowRequests: SwitchPreferenceCompat
2131
private val packageName: String?
2232
get() = arguments?.getString("package")
2333

@@ -30,13 +40,28 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
3040
appHeadingPreference = preferenceScreen.findPreference("pref_safetynet_app_heading") ?: appHeadingPreference
3141
recents = preferenceScreen.findPreference("prefcat_safetynet_recent_list") ?: recents
3242
recentsNone = preferenceScreen.findPreference("pref_safetynet_recent_none") ?: recentsNone
43+
allowRequests = preferenceScreen.findPreference("pref_device_attestation_app_allow_requests") ?: allowRequests
44+
allowRequests.setOnPreferenceChangeListener { _, newValue ->
45+
val playIntegrityDataSet = loadPlayIntegrityData()
46+
val integrityData = packageName?.let { packageName -> playIntegrityDataSet.find { packageName == it.packageName } }
47+
if (newValue is Boolean && integrityData != null) {
48+
val content = PlayIntegrityData.updateDataSetString(playIntegrityDataSet, integrityData.apply { this.allowed = newValue })
49+
VendingPreferences.setPlayIntegrityAppList(requireContext(), content)
50+
}
51+
true
52+
}
3353
}
3454

3555
override fun onResume() {
3656
super.onResume()
3757
updateContent()
3858
}
3959

60+
private fun loadPlayIntegrityData(): Set<PlayIntegrityData> {
61+
val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(requireContext())
62+
return PlayIntegrityData.loadDataSet(playIntegrityData)
63+
}
64+
4065
fun updateContent() {
4166
lifecycleScope.launchWhenResumed {
4267
appHeadingPreference.packageName = packageName
@@ -52,7 +77,6 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
5277
}.orEmpty()
5378
recents.removeAll()
5479
recents.addPreference(recentsNone)
55-
recentsNone.isVisible = summaries.isEmpty()
5680
for (summary in summaries) {
5781
val preference = Preference(requireContext())
5882
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@@ -84,6 +108,23 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
84108
}
85109
recents.addPreference(preference)
86110
}
111+
val piContent = packageName?.let { packageName -> loadPlayIntegrityData().find { packageName == it.packageName } }
112+
if (piContent != null) {
113+
val preference = Preference(requireContext())
114+
val date = DateUtils.getRelativeDateTimeString(
115+
context,
116+
piContent.lastTime,
117+
DateUtils.MINUTE_IN_MILLIS,
118+
DateUtils.WEEK_IN_MILLIS,
119+
DateUtils.FORMAT_SHOW_TIME
120+
)
121+
preference.title = date
122+
preference.summary = piContent.lastResult
123+
preference.icon = if (piContent.lastStatus) ContextCompat.getDrawable(context, R.drawable.ic_circle_check) else ContextCompat.getDrawable(context, R.drawable.ic_circle_warn)
124+
recents.addPreference(preference)
125+
}
126+
recentsNone.isVisible = summaries.isEmpty() && piContent == null
127+
allowRequests.isChecked = piContent?.allowed == true
87128
}
88129

89130
}

play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package org.microg.gms.ui
77

8-
import android.annotation.SuppressLint
98
import android.os.Bundle
109
import android.util.Base64
1110
import android.util.Log
@@ -38,6 +37,8 @@ import org.microg.gms.safetynet.SafetyNetDatabase
3837
import org.microg.gms.safetynet.SafetyNetPreferences
3938
import org.microg.gms.safetynet.SafetyNetRequestType.*
4039
import org.microg.gms.utils.singleInstanceOf
40+
import org.microg.gms.vending.PlayIntegrityData
41+
import org.microg.gms.vending.VendingPreferences
4142
import java.net.URLEncoder
4243
import kotlin.coroutines.resume
4344
import kotlin.coroutines.resumeWithException
@@ -231,13 +232,14 @@ class SafetyNetFragment : PreferenceFragmentCompat() {
231232
lifecycleScope.launchWhenResumed {
232233
val context = requireContext()
233234
val (apps, showAll) = withContext(Dispatchers.IO) {
235+
val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context)
234236
val db = SafetyNetDatabase(context)
235237
val apps = try {
236-
db.recentApps
238+
db.recentApps + PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime }
237239
} finally {
238240
db.close()
239241
}
240-
apps.map { app ->
242+
apps.sortedByDescending { it.second }.map { app ->
241243
app to context.packageManager.getApplicationInfoIfExists(app.first)
242244
}.mapNotNull { (app, info) ->
243245
if (info == null) null else app to info

play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,19 @@ object VendingPreferences {
129129
put(SettingsContract.Vending.APPS_INSTALLER_LIST, content)
130130
}
131131
}
132+
133+
@JvmStatic
134+
fun getPlayIntegrityAppList(context: Context): String {
135+
val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST)
136+
return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c ->
137+
c.getString(0)
138+
}
139+
}
140+
141+
@JvmStatic
142+
fun setPlayIntegrityAppList(context: Context, content: String) {
143+
SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) {
144+
put(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST, content)
145+
}
146+
}
132147
}

play-services-core/src/main/res/navigation/nav_settings.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
<fragment
126126
android:id="@+id/safetyNetFragment"
127127
android:name="org.microg.gms.ui.SafetyNetFragment"
128-
android:label="@string/service_name_snet"
128+
android:label="@string/service_name_device_attestation"
129129
tools:layout="@layout/safety_net_fragment">
130130
<action
131131
android:id="@+id/openSafetyNetAppDetails"
@@ -141,13 +141,13 @@
141141
<fragment
142142
android:id="@+id/safetyNetAdvancedFragment"
143143
android:name="org.microg.gms.ui.SafetyNetAdvancedFragment"
144-
android:label="@string/service_name_snet"
144+
android:label="@string/service_name_device_attestation"
145145
tools:layout="@layout/safety_net_advanced_fragment" />
146146

147147
<fragment
148148
android:id="@+id/safetyNetAllAppsFragment"
149149
android:name="org.microg.gms.ui.SafetyNetAllAppsFragment"
150-
android:label="@string/prefcat_safetynet_apps_title">
150+
android:label="@string/prefcat_device_attestation_apps_title">
151151
<action
152152
android:id="@+id/openSafetyNetAppDetailsFromAll"
153153
app:destination="@id/safetyNetAppFragment" />
@@ -156,7 +156,7 @@
156156
<fragment
157157
android:id="@+id/safetyNetAppFragment"
158158
android:name="org.microg.gms.ui.SafetyNetAppFragment"
159-
android:label="@string/service_name_snet"
159+
android:label="@string/service_name_device_attestation"
160160
tools:layout="@layout/safety_net_app_fragment">
161161
<argument
162162
android:name="package"

play-services-core/src/main/res/values-zh-rCN/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@
136136
<string name="profile_name_user">自定义:%s</string>
137137
<string name="profile_name_auto">自动:%s</string>
138138
<string name="profile_name_system">系统:%s</string>
139+
<string name="pref_device_attestation_app_allow_requests_title">允许请求</string>
140+
<string name="pref_device_attestation_app_allow_requests_summary">允许应用程序请求设备身份验证</string>
139141
<string name="pref_safetynet_test_title">"测试 SafetyNet 认证"</string>
140142
<string name="safetynet_intro">"Google SafetyNet 是一套设备认证系统,旨在确认设备具有适当安全性,并与 Android CTS 兼容。某些应用会出于安全考虑或是防篡改目的而使用 SafetyNet。
141143

@@ -154,6 +156,7 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要
154156
<string name="pref_device_registration_select_profile_title">选择配置信息</string>
155157
<string name="pref_device_registration_device_profile_category">设备配置信息</string>
156158
<string name="prefcat_safetynet_apps_title">使用 SafetyNet 的应用</string>
159+
<string name="prefcat_device_attestation_apps_title">使用设备认证的应用</string>
157160
<string name="menu_clear_recent_requests">清除近期的 SafetyNet 请求</string>
158161
<string name="safetynet_last_run_at">最近使用于<xliff:g example="昨天 02:20 PM">%1$s</xliff:g></string>
159162
<string name="pref_safetynet_recent_eval_type">评估类型</string>
@@ -226,6 +229,7 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要
226229
<string name="pref_accounts_summary">添加和管理 Google 账号</string>
227230
<string name="perm_gsf_read_gservices_label">读取Google服务配置</string>
228231
<string name="service_name_snet">Google SafetyNet</string>
232+
<string name="service_name_device_attestation">设备认证</string>
229233
<string name="pref_safetynet_recent_recaptcha_summary">ReCaptcha: %s</string>
230234
<string name="pref_safetynet_recent_recaptcha_enterprise_summary">ReCaptcha Enterprise: %s</string>
231235
<string name="pref_game_accounts_title">Google 游戏账号</string>

play-services-core/src/main/res/values-zh-rTW/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
<string name="pref_test_summary_running">執行中…</string>
161161
<string name="pref_droidguard_operation_mode">運作模式</string>
162162
<string name="prefcat_safetynet_apps_title">使用 SafetyNet 的應用程式</string>
163+
<string name="prefcat_device_attestation_apps_title">使用設備認證的應用程式</string>
163164
<string name="menu_clear_recent_requests">清除最近的請求</string>
164165
<string name="profile_name_native">原生</string>
165166
<string name="profile_name_real">實機</string>
@@ -196,6 +197,9 @@
196197
<string name="perm_car_vendor_extension_label">車用廠商通訊通道</string>
197198
<string name="perm_car_vendor_extension_description">存取您車輛的車廠專屬通道,以交換與車輛相關的專屬資訊</string>
198199
<string name="service_name_snet">Google SafetyNet</string>
200+
<string name="service_name_device_attestation">設備認證</string>
201+
<string name="pref_device_attestation_app_allow_requests_title">允許請求</string>
202+
<string name="pref_device_attestation_app_allow_requests_summary">允許應用程式請求裝置身份驗證</string>
199203
<string name="pref_auth_strip_device_name_summary">啟用此功能後,驗證請求中將不包含裝置名稱,這可能允許未授權的裝置登入,但也可能導致不可預期的後果。</string>
200204
<string name="pref_info_status">狀態</string>
201205
<string name="pref_more_settings">更多</string>

0 commit comments

Comments
 (0)