Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ object SettingsContract {
const val ASSET_DEVICE_SYNC = "vending_device_sync"
const val APPS_INSTALL = "vending_apps_install"
const val APPS_INSTALLER_LIST = "vending_apps_installer_list"
const val PLAY_INTEGRITY_APP_LIST = "vending_play_integrity_apps"

val PROJECTION = arrayOf(
LICENSING,
Expand All @@ -289,6 +290,7 @@ object SettingsContract {
ASSET_DEVICE_SYNC,
APPS_INSTALL,
APPS_INSTALLER_LIST,
PLAY_INTEGRITY_APP_LIST
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class SettingsProvider : ContentProvider() {
Vending.SPLIT_INSTALL -> getSettingsBoolean(key, false)
Vending.APPS_INSTALL -> getSettingsBoolean(key, false)
Vending.APPS_INSTALLER_LIST -> getSettingsString(key, "")
Vending.PLAY_INTEGRITY_APP_LIST -> getSettingsString(key, "")
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
Expand All @@ -386,6 +387,7 @@ class SettingsProvider : ContentProvider() {
Vending.ASSET_DEVICE_SYNC -> editor.putBoolean(key, value as Boolean)
Vending.APPS_INSTALL -> editor.putBoolean(key, value as Boolean)
Vending.APPS_INSTALLER_LIST -> editor.putString(key, value as String)
Vending.PLAY_INTEGRITY_APP_LIST -> editor.putString(key, value as String)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.vending

import org.json.JSONException
import org.json.JSONObject

class PlayIntegrityData(var allowed: Boolean,
val packageName: String,
val pkgSignSha256: String,
var lastTime: Long,
var lastResult: String? = null,
var lastStatus: Boolean = false) {

override fun toString(): String {
return JSONObject()
.put(ALLOWED, allowed)
.put(PACKAGE_NAME, packageName)
.put(SIGNATURE, pkgSignSha256)
.put(LAST_VISIT_TIME, lastTime)
.put(LAST_VISIT_RESULT, lastResult)
.put(LAST_VISIT_STATUS, lastStatus)
.toString()
}

companion object {
private const val PACKAGE_NAME = "packageName"
private const val ALLOWED = "allowed"
private const val SIGNATURE = "signature"
private const val LAST_VISIT_TIME = "lastVisitTime"
private const val LAST_VISIT_RESULT = "lastVisitResult"
private const val LAST_VISIT_STATUS = "lastVisitStatus"

private fun parse(jsonString: String): PlayIntegrityData? {
try {
val json = JSONObject(jsonString)
return PlayIntegrityData(
json.getBoolean(ALLOWED),
json.getString(PACKAGE_NAME),
json.getString(SIGNATURE),
json.getLong(LAST_VISIT_TIME),
json.getString(LAST_VISIT_RESULT),
json.getBoolean(LAST_VISIT_STATUS)
)
} catch (e: JSONException) {
return null
}
}

fun loadDataSet(content: String): Set<PlayIntegrityData> {
return content.split("|").mapNotNull { parse(it) }.toSet()
}

fun updateDataSetString(channelList: Set<PlayIntegrityData>, channel: PlayIntegrityData): String {
val channelData = channelList.find { it.packageName == channel.packageName && it.pkgSignSha256 == channel.pkgSignSha256 }
val newChannelList = if (channelData != null) {
channelData.allowed = channel.allowed
channelData.lastTime = channel.lastTime
channelData.lastResult = channel.lastResult
channelData.lastStatus = channel.lastStatus
channelList
} else {
channelList + channel
}
return newChannelList.let { it -> it.joinToString(separator = "|") { it.toString() } }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import com.google.android.gms.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.microg.gms.safetynet.SafetyNetDatabase
import org.microg.gms.vending.PlayIntegrityData
import org.microg.gms.vending.VendingPreferences

class SafetyNetAllAppsFragment : PreferenceFragmentCompat() {
private lateinit var database: SafetyNetDatabase
Expand Down Expand Up @@ -50,8 +52,10 @@ class SafetyNetAllAppsFragment : PreferenceFragmentCompat() {
private fun updateContent() {
val context = requireContext()
lifecycleScope.launchWhenResumed {
val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context)
val apps = withContext(Dispatchers.IO) {
val res = database.recentApps.map { app ->
val playPairs = PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime }
val res = (database.recentApps + playPairs).map { app ->
val pref = AppIconPreference(context)
pref.packageName = app.first
pref.summary = when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,26 @@ package org.microg.gms.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.format.DateUtils
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.*
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.isEmpty
import com.google.android.gms.R
import org.microg.gms.safetynet.SafetyNetDatabase
import org.microg.gms.safetynet.SafetyNetRequestType.*
import org.microg.gms.safetynet.SafetyNetRequestType.ATTESTATION
import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA
import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA_ENTERPRISE
import org.microg.gms.vending.PlayIntegrityData
import org.microg.gms.vending.VendingPreferences

class SafetyNetAppFragment : PreferenceFragmentCompat() {
private lateinit var appHeadingPreference: AppHeadingPreference
private lateinit var recents: PreferenceCategory
private lateinit var recentsNone: Preference
private lateinit var recentRequestAllow: SwitchPreferenceCompat
private val packageName: String?
get() = arguments?.getString("package")

Expand All @@ -30,13 +40,28 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
appHeadingPreference = preferenceScreen.findPreference("pref_safetynet_app_heading") ?: appHeadingPreference
recents = preferenceScreen.findPreference("prefcat_safetynet_recent_list") ?: recents
recentsNone = preferenceScreen.findPreference("pref_safetynet_recent_none") ?: recentsNone
recentRequestAllow = preferenceScreen.findPreference("pref_safetynet_app_allow_request") ?: recentRequestAllow
recentRequestAllow.setOnPreferenceChangeListener { _, newValue ->
val playIntegrityDataSet = loadPlayIntegrityData()
val integrityData = packageName?.let { packageName -> playIntegrityDataSet.find { packageName == it.packageName } }
if (newValue is Boolean && integrityData != null) {
val content = PlayIntegrityData.updateDataSetString(playIntegrityDataSet, integrityData.apply { this.allowed = newValue })
VendingPreferences.setPlayIntegrityAppList(requireContext(), content)
}
true
}
}

override fun onResume() {
super.onResume()
updateContent()
}

private fun loadPlayIntegrityData(): Set<PlayIntegrityData> {
val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(requireContext())
return PlayIntegrityData.loadDataSet(playIntegrityData)
}

fun updateContent() {
lifecycleScope.launchWhenResumed {
appHeadingPreference.packageName = packageName
Expand All @@ -52,7 +77,6 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
}.orEmpty()
recents.removeAll()
recents.addPreference(recentsNone)
recentsNone.isVisible = summaries.isEmpty()
for (summary in summaries) {
val preference = Preference(requireContext())
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Expand Down Expand Up @@ -84,6 +108,23 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
}
recents.addPreference(preference)
}
val piContent = packageName?.let { packageName -> loadPlayIntegrityData().find { packageName == it.packageName } }
if (piContent != null) {
val preference = Preference(requireContext())
val date = DateUtils.getRelativeDateTimeString(
context,
piContent.lastTime,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.WEEK_IN_MILLIS,
DateUtils.FORMAT_SHOW_TIME
)
preference.title = date
preference.summary = piContent.lastResult
preference.icon = if (piContent.lastStatus) ContextCompat.getDrawable(context, R.drawable.ic_circle_check) else ContextCompat.getDrawable(context, R.drawable.ic_circle_warn)
recents.addPreference(preference)
}
recentsNone.isVisible = recents.isEmpty()
recentRequestAllow.isVisible = piContent != null
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package org.microg.gms.ui

import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Base64
import android.util.Log
Expand Down Expand Up @@ -38,6 +37,8 @@ import org.microg.gms.safetynet.SafetyNetDatabase
import org.microg.gms.safetynet.SafetyNetPreferences
import org.microg.gms.safetynet.SafetyNetRequestType.*
import org.microg.gms.utils.singleInstanceOf
import org.microg.gms.vending.PlayIntegrityData
import org.microg.gms.vending.VendingPreferences
import java.net.URLEncoder
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
Expand Down Expand Up @@ -231,9 +232,10 @@ class SafetyNetFragment : PreferenceFragmentCompat() {
lifecycleScope.launchWhenResumed {
val context = requireContext()
val (apps, showAll) = withContext(Dispatchers.IO) {
val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context)
val db = SafetyNetDatabase(context)
val apps = try {
db.recentApps
db.recentApps + PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime }
} finally {
db.close()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,19 @@ object VendingPreferences {
put(SettingsContract.Vending.APPS_INSTALLER_LIST, content)
}
}

@JvmStatic
fun getPlayIntegrityAppList(context: Context): String {
val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST)
return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c ->
c.getString(0)
}
}

@JvmStatic
fun setPlayIntegrityAppList(context: Context, content: String) {
SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) {
put(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST, content)
}
}
}
6 changes: 4 additions & 2 deletions play-services-core/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
<string name="profile_name_user">自定义:%s</string>
<string name="profile_name_auto">自动:%s</string>
<string name="profile_name_system">系统:%s</string>
<string name="pref_safetynet_app_allow_request_title">允许请求</string>
<string name="pref_safetynet_app_allow_request_summary">允许应用程序请求设备身份验证</string>
<string name="pref_safetynet_test_title">"测试 SafetyNet 认证"</string>
<string name="safetynet_intro">"Google SafetyNet 是一套设备认证系统,旨在确认设备具有适当安全性,并与 Android CTS 兼容。某些应用会出于安全考虑或是防篡改目的而使用 SafetyNet。

Expand All @@ -153,7 +155,7 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要
<string name="pref_device_registration_import_custom_profile_summary">从文件导入自定义的设备配置信息</string>
<string name="pref_device_registration_select_profile_title">选择配置信息</string>
<string name="pref_device_registration_device_profile_category">设备配置信息</string>
<string name="prefcat_safetynet_apps_title">使用 SafetyNet 的应用</string>
<string name="prefcat_safetynet_apps_title">使用设备认证的应用</string>
<string name="menu_clear_recent_requests">清除近期的 SafetyNet 请求</string>
<string name="safetynet_last_run_at">最近使用于<xliff:g example="昨天 02:20 PM">%1$s</xliff:g></string>
<string name="pref_safetynet_recent_eval_type">评估类型</string>
Expand Down Expand Up @@ -225,7 +227,7 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要
<string name="pref_safetynet_recent_attestation_summary">证明:%s</string>
<string name="pref_accounts_summary">添加和管理 Google 账号</string>
<string name="perm_gsf_read_gservices_label">读取Google服务配置</string>
<string name="service_name_snet">Google SafetyNet</string>
<string name="service_name_snet">Google 设备认证</string>
<string name="pref_safetynet_recent_recaptcha_summary">ReCaptcha: %s</string>
<string name="pref_safetynet_recent_recaptcha_enterprise_summary">ReCaptcha Enterprise: %s</string>
<string name="pref_game_accounts_title">Google 游戏账号</string>
Expand Down
6 changes: 4 additions & 2 deletions play-services-core/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
<string name="pref_test_summary_warn">警告:%s</string>
<string name="pref_test_summary_running">執行中…</string>
<string name="pref_droidguard_operation_mode">運作模式</string>
<string name="prefcat_safetynet_apps_title">使用 SafetyNet 的應用程式</string>
<string name="prefcat_safetynet_apps_title">使用設備認證的應用程式</string>
<string name="menu_clear_recent_requests">清除最近的請求</string>
<string name="profile_name_native">原生</string>
<string name="profile_name_real">實機</string>
Expand Down Expand Up @@ -195,7 +195,9 @@
<string name="perm_car_mileage_description">存取您的車輛行駛里程</string>
<string name="perm_car_vendor_extension_label">車用廠商通訊通道</string>
<string name="perm_car_vendor_extension_description">存取您車輛的車廠專屬通道,以交換與車輛相關的專屬資訊</string>
<string name="service_name_snet">Google SafetyNet</string>
<string name="service_name_snet">Google 設備認證</string>
<string name="pref_safetynet_app_allow_request_title">允許請求</string>
<string name="pref_safetynet_app_allow_request_summary">允許應用程式請求裝置身份驗證</string>
<string name="pref_auth_strip_device_name_summary">啟用此功能後,驗證請求中將不包含裝置名稱,這可能允許未授權的裝置登入,但也可能導致不可預期的後果。</string>
<string name="pref_info_status">狀態</string>
<string name="pref_more_settings">更多</string>
Expand Down
6 changes: 4 additions & 2 deletions play-services-core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Please set up a password, PIN, or pattern lock screen."</string>

<string name="service_name_checkin">Google device registration</string>
<string name="service_name_mcs">Cloud Messaging</string>
<string name="service_name_snet">Google SafetyNet</string>
<string name="service_name_snet">Google Device Attestation</string>
<string name="service_name_vending">Play Store services</string>
<string name="service_name_work_profile">Work profile</string>

Expand Down Expand Up @@ -238,7 +238,9 @@ Please set up a password, PIN, or pattern lock screen."</string>
<string name="pref_test_summary_running">Running…</string>
<string name="pref_droidguard_operation_mode">Operation mode</string>
<string name="pref_droidguard_unsupported_summary">DroidGuard execution is unsupported on this device. SafetyNet services may misbehave.</string>
<string name="prefcat_safetynet_apps_title">Apps using SafetyNet</string>
<string name="prefcat_safetynet_apps_title">Apps using Device Attestation</string>
<string name="pref_safetynet_app_allow_request_title">Allow Request</string>
<string name="pref_safetynet_app_allow_request_summary">Allow the app to request device authentication</string>
<string name="menu_clear_recent_requests">Clear recent requests</string>
<string name="safetynet_last_run_at">Last use: <xliff:g example="Yesterday, 02:20 PM">%1$s</xliff:g></string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
tools:title="@tools:sample/lorem"
app:allowDividerBelow="false" />

<SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_safetynet_app_allow_request"
android:persistent="false"
android:summary="@string/pref_safetynet_app_allow_request_summary"
android:title="@string/pref_safetynet_app_allow_request_title"
app:iconSpaceReserved="false"
app:isPreferenceVisible="false"/>

<PreferenceCategory
android:key="prefcat_safetynet_recent_list"
android:title="@string/pref_safetynet_recent_uses"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,26 @@ object VendingPreferences {
put(SettingsContract.Vending.APPS_INSTALLER_LIST, content)
}
}

@JvmStatic
fun isDeviceAttestationEnabled(context: Context): Boolean {
return SettingsContract.getSettings(context, SettingsContract.SafetyNet.getContentUri(context), SettingsContract.SafetyNet.PROJECTION) { c ->
c.getInt(0) != 0
}
}

@JvmStatic
fun getPlayIntegrityAppList(context: Context): String {
val projection = arrayOf(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST)
return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c ->
c.getString(0)
}
}

@JvmStatic
fun setPlayIntegrityAppList(context: Context, content: String) {
SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) {
put(SettingsContract.Vending.PLAY_INTEGRITY_APP_LIST, content)
}
}
}
Loading