EN: Don't allow apps to turn off EN that didn't turn it on before

This commit is contained in:
Marvin W 2020-12-12 12:40:48 +01:00
parent 9b91bf63c6
commit 0cd028af0e
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
6 changed files with 94 additions and 30 deletions

View File

@ -6,7 +6,7 @@
buildscript {
ext.nlpVersion = '2.0-alpha5'
ext.remoteDroidGuardVersion = '0.1.2'
ext.safeParcelVersion = '1.6.0'
ext.safeParcelVersion = '1.7.0'
ext.wearableVersion = '0.1.1'
ext.kotlinVersion = '1.4.10'

View File

@ -41,13 +41,13 @@ import java.util.Map;
*/
@PublicApi
public class DailySummariesConfig extends AutoSafeParcelable {
@Field(1)
@Field(value = 1, useDirectList = true)
private List<Double> reportTypeWeights;
@Field(2)
@Field(value = 2, useDirectList = true)
private List<Double> infectiousnessWeights;
@Field(3)
@Field(value = 3, useDirectList = true)
private List<Integer> attenuationBucketThresholdDb;
@Field(4)
@Field(value = 4, useDirectList = true)
private List<Double> attenuationBucketWeights;
@Field(5)
private int daysSinceExposureThreshold;

View File

@ -11,6 +11,7 @@ package com.google.android.gms.nearby.exposurenotification;
import org.microg.gms.common.PublicApi;
import org.microg.safeparcel.AutoSafeParcelable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@ -21,8 +22,8 @@ import java.util.Map;
*/
@PublicApi
public class DiagnosisKeysDataMapping extends AutoSafeParcelable {
@Field(1)
private List<Integer> daysSinceOnsetToInfectiousness;
@Field(value = 1, useDirectList = true)
private List<Integer> daysSinceOnsetToInfectiousness = new ArrayList<>();
@Field(2)
@ReportType
private int reportTypeWhenMissing;
@ -82,7 +83,7 @@ public class DiagnosisKeysDataMapping extends AutoSafeParcelable {
if (infectiousnessWhenDaysSinceOnsetMissing == null)
throw new IllegalStateException("Must set infectiousnessWhenDaysSinceOnsetMissing");
DiagnosisKeysDataMapping mapping = new DiagnosisKeysDataMapping();
mapping.daysSinceOnsetToInfectiousness = daysSinceOnsetToInfectiousness;
mapping.daysSinceOnsetToInfectiousness = new ArrayList<>(daysSinceOnsetToInfectiousness);
mapping.reportTypeWhenMissing = reportTypeWhenMissing;
mapping.infectiousnessWhenDaysSinceOnsetMissing = infectiousnessWhenDaysSinceOnsetMissing;
return mapping;

View File

@ -6,6 +6,7 @@
package org.microg.gms.nearby.exposurenotification
import android.annotation.TargetApi
import android.util.Log
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import java.nio.ByteBuffer
import java.nio.ByteOrder
@ -45,7 +46,7 @@ fun getPeriodInDay(intervalNumber: Int) = intervalNumber - getDayRollingStartNum
val nextKeyMillis: Long
get() {
val currentWindowStart = currentIntervalNumber * ROLLING_WINDOW_LENGTH * 1000
val currentWindowStart = currentIntervalNumber.toLong() * ROLLING_WINDOW_LENGTH * 1000
val currentWindowEnd = currentWindowStart + ROLLING_WINDOW_LENGTH * 1000
return (currentWindowEnd - System.currentTimeMillis()).coerceAtLeast(0)
}

View File

@ -20,6 +20,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import kotlinx.coroutines.*
import okio.ByteString
import org.microg.gms.common.PackageUtils
import java.io.File
import java.lang.Runnable
import java.nio.ByteBuffer
@ -61,7 +62,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
db.execSQL("CREATE TABLE $TABLE_APP_PERMS(package TEXT NOT NULL, sig TEXT NOT NULL, perm TEXT NOT NULL, timestamp INTEGER NOT NULL);")
}
if (oldVersion < 5) {
Log.d(TAG, "Creating tables for version >= 3")
Log.d(TAG, "Creating tables for version >= 5")
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TOKENS(tid INTEGER PRIMARY KEY, package TEXT NOT NULL, token TEXT NOT NULL, timestamp INTEGER NOT NULL, configuration BLOB, diagnosisKeysDataMap BLOB);")
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TOKENS}_package_token ON $TABLE_TOKENS(package, token);")
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE(tcsid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER);")
@ -76,6 +77,10 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_tcfid ON $TABLE_TEK_CHECK_FILE_MATCH(tcfid);")
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_key ON $TABLE_TEK_CHECK_FILE_MATCH(keyData, rollingStartNumber, rollingPeriod);")
}
if (oldVersion < 9) {
Log.d(TAG, "Creating tables for version >= 9")
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_APP(package TEXT NOT NULL, sig TEXT NOT NULL, PRIMARY KEY (package, sig));")
}
if (oldVersion in 5 until 7) {
Log.d(TAG, "Altering tables for version >= 7")
db.execSQL("ALTER TABLE $TABLE_TOKENS ADD COLUMN diagnosisKeysDataMap BLOB;")
@ -97,6 +102,22 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
// Entries might be invalid due to previously missing support for new bluetooth AEM format
db.execSQL("DELETE FROM $TABLE_TEK_CHECK_FILE WHERE tcfid NOT IN (SELECT tcfid FROM $TABLE_TEK_CHECK_FILE_MATCH);")
}
if (oldVersion in 1 until 9) {
Log.d(TAG, "Migrating authorized apps from version < 9")
val pm = context.packageManager
db.query(true, TABLE_APP_LOG, arrayOf("package"), null, null, null, null, null, null).use { cursor ->
while (cursor.moveToNext()) {
val packageName = cursor.getString(0)
val signatureDigest = PackageUtils.firstSignatureDigest(pm, packageName)
if (signatureDigest != null) {
db.insertWithOnConflict(TABLE_APP, "NULL", ContentValues().apply {
put("package", packageName)
put("sig", signatureDigest)
}, CONFLICT_IGNORE)
}
}
}
}
Log.d(TAG, "Finished database upgrade")
}
@ -177,6 +198,21 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
}, null, null)
}
fun authorizeApp(packageName: String?, signatureDigest: String? = PackageUtils.firstSignatureDigest(context, packageName)) = writableDatabase.run {
if (packageName == null || signatureDigest == null) return@run
insertWithOnConflict(TABLE_APP, "NULL", ContentValues().apply {
put("package", packageName)
put("sig", signatureDigest)
}, CONFLICT_IGNORE)
}
fun isAppAuthorized(packageName: String?, signatureDigest: String? = PackageUtils.firstSignatureDigest(context, packageName)): Boolean = readableDatabase.run {
if (packageName == null || signatureDigest == null) return@run false
query(TABLE_APP, arrayOf("package"), "package = ? AND sig = ?", arrayOf(packageName, signatureDigest), null, null, null).use { cursor ->
return@use cursor.moveToNext()
}
}
fun noteAppAction(packageName: String, method: String, args: String? = null, timestamp: Long = Date().time) = writableDatabase.run {
insert(TABLE_APP_LOG, "NULL", ContentValues().apply {
put("package", packageName)
@ -186,7 +222,6 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
})
}
private fun storeOwnKey(key: TemporaryExposureKey, database: SQLiteDatabase = writableDatabase) = database.run {
insert(TABLE_TEK, "NULL", ContentValues().apply {
put("keyData", key.keyData)
@ -746,7 +781,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
private fun ensureTemporaryExposureKey(): TemporaryExposureKey = writableDatabase.let { database ->
database.beginTransactionNonExclusive()
try {
var key = findOwnKeyAt(currentIntervalNumber.toInt(), database)
var key = findOwnKeyAt(currentIntervalNumber, database)
if (key == null) {
key = generateCurrentDayTemporaryExposureKey()
storeOwnKey(key, database)
@ -760,12 +795,12 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
val currentRpiId: UUID?
get() {
val key = findOwnKeyAt(currentIntervalNumber.toInt()) ?: return null
val buffer = ByteBuffer.wrap(key.generateRpiId(currentIntervalNumber.toInt()))
val key = findOwnKeyAt(currentIntervalNumber) ?: return null
val buffer = ByteBuffer.wrap(key.generateRpiId(currentIntervalNumber))
return UUID(buffer.long, buffer.long)
}
fun generateCurrentPayload(metadata: ByteArray) = ensureTemporaryExposureKey().generatePayload(currentIntervalNumber.toInt(), metadata)
fun generateCurrentPayload(metadata: ByteArray) = ensureTemporaryExposureKey().generatePayload(currentIntervalNumber, metadata)
override fun getWritableDatabase(): SQLiteDatabase {
requirePrimary(this)
@ -791,10 +826,11 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
companion object {
private const val DB_NAME = "exposure.db"
private const val DB_VERSION = 8
private const val DB_VERSION = 9
private const val DB_SIZE_TOO_LARGE = 256L * 1024 * 1024
private const val MAX_DELETE_TIME = 5000L
private const val TABLE_ADVERTISEMENTS = "advertisements"
private const val TABLE_APP = "app"
private const val TABLE_APP_LOG = "app_log"
private const val TABLE_TEK = "tek"
private const val TABLE_APP_PERMS = "app_perms"

View File

@ -101,15 +101,14 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
}
override fun start(params: StartParams) {
if (ExposurePreferences(context).enabled) {
params.callback.onResult(Status.SUCCESS)
return
}
lifecycleScope.launchWhenStarted {
val status = confirmPermission(CONFIRM_ACTION_START)
if (status.isSuccess) {
ExposurePreferences(context).enabled = true
ExposureDatabase.with(context) { database -> database.noteAppAction(packageName, "start") }
ExposureDatabase.with(context) { database ->
database.authorizeApp(packageName)
database.noteAppAction(packageName, "start")
}
}
try {
params.callback.onResult(status)
@ -121,9 +120,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
override fun stop(params: StopParams) {
lifecycleScope.launchWhenStarted {
ExposurePreferences(context).enabled = false
ExposureDatabase.with(context) { database ->
database.noteAppAction(packageName, "stop")
val isAuthorized = ExposureDatabase.with(context) { database ->
database.isAppAuthorized(packageName).also {
if (it) database.noteAppAction(packageName, "stop")
}
}
if (isAuthorized) {
ExposurePreferences(context).enabled = false
}
try {
params.callback.onResult(Status.SUCCESS)
@ -134,10 +137,17 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
}
override fun isEnabled(params: IsEnabledParams) {
try {
params.callback.onResult(Status.SUCCESS, ExposurePreferences(context).enabled)
} catch (e: Exception) {
Log.w(TAG, "Callback failed", e)
lifecycleScope.launchWhenStarted {
val isAuthorized = ExposureDatabase.with(context) { database ->
database.isAppAuthorized(packageName).also {
if (it) database.noteAppAction(packageName, "isEnabled")
}
}
try {
params.callback.onResult(Status.SUCCESS, isAuthorized && ExposurePreferences(context).enabled)
} catch (e: Exception) {
Log.w(TAG, "Callback failed", e)
}
}
}
@ -146,6 +156,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
val status = confirmPermission(CONFIRM_ACTION_KEYS)
val response = when {
status.isSuccess -> ExposureDatabase.with(context) { database ->
database.authorizeApp(packageName)
database.exportKeys()
}
else -> emptyList()
@ -194,6 +205,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
?: ExposureConfiguration.ExposureConfigurationBuilder().build()
private suspend fun buildExposureSummary(token: String): ExposureSummary = ExposureDatabase.with(context) { database ->
if (!database.isAppAuthorized(packageName)) {
// Not providing summary if app not authorized
Log.d(TAG, "$packageName not yet authorized")
return@with ExposureSummary.ExposureSummaryBuilder().build()
}
val pair = database.loadConfiguration(packageName, token)
val (configuration, exposures) = if (pair != null) {
pair.second.orDefault() to database.findAllMeasuredExposures(pair.first).merge()
@ -334,6 +350,12 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
put("request_keys_count", keys)
}.toString())
if (!database.isAppAuthorized(packageName)) {
// Not sending results via broadcast if app not authorized
Log.d(TAG, "$packageName not yet authorized")
return@with
}
val exposureSummary = buildExposureSummary(token)
try {
@ -381,11 +403,13 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
lifecycleScope.launchWhenStarted {
ExposureDatabase.with(context) { database ->
val pair = database.loadConfiguration(packageName, params.token)
val response = if (pair != null) {
val response = if (pair != null && database.isAppAuthorized(packageName)) {
database.findAllMeasuredExposures(pair.first).merge().map {
it.toExposureInformation(pair.second.orDefault())
}
} else {
// Not providing information if app not authorized
Log.d(TAG, "$packageName not yet authorized")
emptyList()
}
@ -425,9 +449,11 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
private suspend fun getExposureWindowsInternal(token: String = TOKEN_A): List<ExposureWindow> {
val (exposures, mapping) = ExposureDatabase.with(context) { database ->
val triple = database.loadConfiguration(packageName, token)
if (triple != null) {
if (triple != null && database.isAppAuthorized(packageName)) {
database.findAllMeasuredExposures(triple.first).merge() to triple.third.orDefault()
} else {
// Not providing windows if app not authorized
Log.d(TAG, "$packageName not yet authorized")
emptyList<MergedExposure>() to DiagnosisKeysDataMapping()
}
}